Initial commit from SVN.

This commit is contained in:
wcrisman
2014-05-30 10:31:51 -07:00
commit b45e56b890
1968 changed files with 370949 additions and 0 deletions

View File

@@ -0,0 +1,11 @@
<?xml version="1.0" encoding="UTF-8"?>
<classpath>
<classpathentry kind="src" path="src"/>
<classpathentry kind="src" path="/Foundation TCV"/>
<classpathentry kind="src" path="/Common"/>
<classpathentry kind="src" path="/Class File Services"/>
<classpathentry kind="src" path="/Foundation"/>
<classpathentry kind="src" path="/Orb"/>
<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER"/>
<classpathentry kind="output" path="bin"/>
</classpath>

View File

@@ -0,0 +1,23 @@
<?xml version="1.0" encoding="UTF-8"?>
<projectDescription>
<name>Foundation TCV Server</name>
<comment></comment>
<projects>
<project>Class File Services</project>
<project>Common</project>
<project>Foundation</project>
<project>Foundation TCV</project>
<project>Orb</project>
<project>SWT</project>
</projects>
<buildSpec>
<buildCommand>
<name>org.eclipse.jdt.core.javabuilder</name>
<arguments>
</arguments>
</buildCommand>
</buildSpec>
<natures>
<nature>org.eclipse.jdt.core.javanature</nature>
</natures>
</projectDescription>

View File

@@ -0,0 +1,30 @@
/*
* Copyright (c) 2006 Declarative Engineering LLC.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Declarative Engineering LLC
* verson 1 which accompanies this distribution, and is available at
* http://declarativeengineering.com/legal/DE_Developer_License_v1.txt
*/
package com.foundation.tcv.server.controller;
import com.foundation.application.Application;
import com.foundation.view.resource.AbstractResourceService;
public class ApplicationBrowserService extends ServerController {
/** The reference to the application that initialized this service. */
private Application application;
/**
* ApplicationBrowserService constructor.
* @param application The reference to the application that initialized this service.
*/
public ApplicationBrowserService(Application application) {
super();
this.application = application;
}//ApplicationBrowserService()//
/* (non-Javadoc)
* @see com.foundation.tcv.server.controller.ServerController#getResourceService()
*/
protected AbstractResourceService getResourceService() {
return application.getResourceService();
}//getResourceService()//
}//ApplicationBrowserService//

View File

@@ -0,0 +1,22 @@
/*
* Copyright (c) 2005,2009 Declarative Engineering LLC.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Declarative Engineering LLC
* verson 1 which accompanies this distribution, and is available at
* http://declarativeengineering.com/legal/DE_Developer_License_v1.txt
*/
package com.foundation.tcv.server.controller;
import com.foundation.view.IViewContext;
import com.foundation.controller.RemoteViewController;
/**
* Defines a view factory which creates an initial view when a new client connects and requests the initial view.
*/
public interface IInitialViewFactory {
/**
* Creates an initial view for a newly connected client.
* @return The new application view controller.
*/
public RemoteViewController createInitialView(IViewContext viewContext);
}//IInitialViewFactory//

View File

@@ -0,0 +1,332 @@
/*
* Copyright (c) 2003,2007 Declarative Engineering LLC.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Declarative Engineering LLC
* verson 1 which accompanies this distribution, and is available at
* http://declarativeengineering.com/legal/DE_Developer_License_v1.txt
*/
package com.foundation.tcv.server.controller;
import java.io.File;
import java.io.FileFilter;
import com.common.io.*;
import com.common.orb.*;
import com.common.security.Sha1;
import com.common.thread.Monitor;
import com.common.util.*;
import com.common.util.optimized.*;
import com.common.debug.*;
import com.foundation.tcv.controller.*;
import com.foundation.util.IManagedList;
import com.foundation.view.resource.AbstractResourceService;
/*
* TODO: Weed out old sessions; Provide a way for a session to reconnect if a client temporarily looses a connection.
*/
public abstract class ServerController implements IThinServerController {
private static final String LICENSE_COMPANY_NAME = "companyName";
private static final String LICENSE_APPLICATION_NAME = "applicationName";
private static final String LICENSE_INSTALLATION_COUNT = "installationCount";
private LongObjectHashMap sessionControllerMap = new LongObjectHashMap(200);
/** A mapping of served application metadata indexed by the application's name. */
private LiteHashMap applicationMap = new LiteHashMap(10);
/** The next available client number, used to track client connections. */
private long nextClientNumber = 1;
private static final class ApplicationData {
private IInitialViewFactory initialViewFactory = null;
//private byte[] license = null;
private String requiredApplicationBrowserVersion = null;
private String requiredApplicationVersion = null;
private String futureApplicationBrowserVersion = null;
private String futureApplicationVersion = null;
/** The collection of download server address strings. This may be modified externally. */
private IManagedList downloadServerAddresses = null;
/** The collection of application server address strings. This may be modified externally. */
private IManagedList applicationServerAddresses = null;
/** The directory containing jar's and zips used by the client. */
private File codeDirectory = null;
/** The name and hashcodes for each of the code jar or zip's. */
private String[] codeMetadata = null;
private ApplicationData(IInitialViewFactory initialViewFactory, Integer count, byte[] license, String requiredApplicationBrowserVersion, String requiredApplicationVersion, String futureApplicationBrowserVersion, String futureApplicationVersion, IManagedList downloadServerAddresses, IManagedList applicationServerAddresses, File codeDirectory) {
this.initialViewFactory = initialViewFactory;
//this.license = license;
this.requiredApplicationBrowserVersion = requiredApplicationBrowserVersion;
this.requiredApplicationVersion = requiredApplicationVersion;
this.futureApplicationBrowserVersion = futureApplicationBrowserVersion;
this.futureApplicationVersion = futureApplicationVersion;
this.downloadServerAddresses = downloadServerAddresses;
this.applicationServerAddresses = applicationServerAddresses;
this.codeDirectory = codeDirectory;
//Setup the code directory metadata.//
if(codeDirectory != null) {
if(codeDirectory.exists() && codeDirectory.isDirectory()) {
int metadataIndex = 0;
File[] archives = codeDirectory.listFiles(new FileFilter() {
public boolean accept(File pathname) {
return pathname.isFile() && pathname.canRead() && ((pathname.getName().endsWith(".zip")) || (pathname.getName().endsWith(".jar")));
}//accept()//
});
codeMetadata = new String[archives.length * 2];
for(int index = 0; index < archives.length; index++) {
codeMetadata[metadataIndex++] = archives[index].getName();
codeMetadata[metadataIndex++] = FileSupport.getFileHash(archives[index], new Sha1());
}//for//
}//if//
else {
throw new IllegalArgumentException("Must pass a valid code directory, or null. The value passed was: '" + codeDirectory + "'");
}//else//
}//if//
if((requiredApplicationBrowserVersion == null) || (requiredApplicationVersion == null)) {
throw new IllegalArgumentException("Must pass a version tag for the required application browser version and the required application version.");
}//if//
if(downloadServerAddresses == null) {
throw new IllegalArgumentException("Must pass a valid collection of address strings in the form of url:port (Example: mycompany.com:9008 or 192.168.1.100:9008).");
}//if//
if((applicationServerAddresses == null) || (applicationServerAddresses.getSize() == 0)) {
throw new IllegalArgumentException("Must pass a valid collection of address strings in the form of url:port (Example: mycompany.com:9008 or 192.168.1.100:9008).");
}//if//
}//ApplicationData()//
}//ApplicationData//
/**
* Registers an unlicensed application that will be served by this process.
* <p>TODO: Allow multiple client versions to be specified, or perhaps a range.</p>
* @param applicationName The name of the application as requested by the client. For licensed applications this will be the "(company name):(application name)".
* @param initialViewFactory The factory object that will produce the initial view for the application.
* @param requiredApplicationBrowserVersion The application browser version required by the application.
* @param requiredApplicationVersion The application version (versions resources specific to the application and installed by the application's installer).
* @param futureApplicationBrowserVersion The application browser version that will be required soon. This may be null if unkown.
* @param futureApplicationVersion The application version that will be required soon. This may be null if unkown.
* @param downloadServerAddresses The collection of address strings (example: "mycompany.com:9009" or "192.168.1.100:9009") for the download servers.
* @param applicationServerAddresses The collection of address strings (example: "mycompany.com:9009" or "192.168.1.100:9009") for the application servers.
* @param codeDirectory The directory where the code for the custom componenets can be found.
*/
public synchronized void registerApplication(String applicationName, IInitialViewFactory initialViewFactory, String requiredApplicationBrowserVersion, String requiredApplicationVersion, String futureApplicationBrowserVersion, String futureApplicationVersion, IManagedList downloadServerAddresses, IManagedList applicationServerAddresses, File codeDirectory) throws java.io.IOException {
Debug.log("Registering an application browser connector. Browser Ver: " + requiredApplicationBrowserVersion + " App Ver: " + requiredApplicationVersion);
applicationMap.put(applicationName, new ApplicationData(initialViewFactory, null, null, requiredApplicationBrowserVersion, requiredApplicationVersion, futureApplicationBrowserVersion, futureApplicationVersion, downloadServerAddresses, applicationServerAddresses, codeDirectory));
}//registerApplication()//
/**
* Registers an application that will be served by this process.
* <p>TODO: Allow multiple client versions to be specified, or perhaps a range.</p>
* @deprecated No longer using a license.
* @param licenseFile The license file containing signed information about the application.
* @param initialViewFactory The factory object that will produce the initial view for the application.
* @param requiredApplicationBrowserVersion The application browser version required by the application.
* @param requiredApplicationVersion The application version (versions resources specific to the application and installed by the application's installer).
* @param futureApplicationBrowserVersion The application browser version that will be required soon. This may be null if unkown.
* @param futureApplicationVersion The application version that will be required soon. This may be null if unkown.
* @param downloadServerAddresses The collection of address strings (example: "mycompany.com:9009" or "192.168.1.100:9009") for the download servers.
* @param applicationServerAddresses The collection of address strings (example: "mycompany.com:9009" or "192.168.1.100:9009") for the application servers.
*/
public synchronized void registerApplication(java.io.File licenseFile, IInitialViewFactory initialViewFactory, String requiredApplicationBrowserVersion, String requiredApplicationVersion, String futureApplicationBrowserVersion, String futureApplicationVersion, IManagedList downloadServerAddresses, IManagedList applicationServerAddresses) throws java.io.IOException {
if((licenseFile.exists()) && (licenseFile.isFile())) {
java.io.FileInputStream fin = new java.io.FileInputStream(licenseFile);
byte[] licenseData = new byte[fin.available()];
int count = 0;
while(count < licenseData.length) {
count += fin.read(licenseData);
}//while//
registerApplication(licenseData, initialViewFactory, requiredApplicationBrowserVersion, requiredApplicationVersion, futureApplicationBrowserVersion, futureApplicationVersion, downloadServerAddresses, applicationServerAddresses);
}//if//
else {
Debug.log("Error: Invalid license file. The application will not be registered.");
}//else//
}//registerApplication()//
/**
* Registers an application that will be served by this process.
* <p>TODO: Allow multiple client versions to be specified, or perhaps a range.</p>
* @deprecated No longer using a license.
* @param licenseData The license data containing signed information about the application.
* @param initialViewFactory The factory object that will produce the initial view for the application.
* @param requiredApplicationBrowserVersion The application browser version required by the application.
* @param requiredApplicationVersion The application version (versions resources specific to the application and installed by the application's installer).
* @param futureApplicationBrowserVersion The application browser version that will be required soon. This may be null if unkown.
* @param futureApplicationVersion The application version that will be required soon. This may be null if unkown.
* @param downloadServerAddresses The collection of address strings (example: "mycompany.com:9009" or "192.168.1.100:9009") for the download servers. This collection may be altered externally if it's monitor is locked.
* @param applicationServerAddresses The collection of address strings (example: "mycompany.com:9009" or "192.168.1.100:9009") for the application servers. This collection may be altered externally if it's monitor is locked.
*/
public synchronized void registerApplication(byte[] licenseData, IInitialViewFactory initialViewFactory, String requiredApplicationBrowserVersion, String requiredApplicationVersion, String futureApplicationBrowserVersion, String futureApplicationVersion, IManagedList downloadServerAddresses, IManagedList applicationServerAddresses) throws java.io.IOException {
ObjectInputStream in = new ObjectInputStream(new ByteArrayInputStream(licenseData), null, null);
LiteHashMap map = null;
String name = null;
Integer installCount = null;
try {
map = (LiteHashMap) in.readObject();
in.close();
}//try//
catch(ClassNotFoundException e) {
Debug.log(e); //Should never happen.//
}//catch//
if(map != null) {
name = ((String) map.get(LICENSE_COMPANY_NAME)) + ':' + ((String) map.get(LICENSE_APPLICATION_NAME));
installCount = (Integer) map.get(LICENSE_INSTALLATION_COUNT);
}//if//
if((name != null) && (installCount != null)) {
applicationMap.put(name, new ApplicationData(initialViewFactory, installCount, licenseData, requiredApplicationBrowserVersion, requiredApplicationVersion, futureApplicationBrowserVersion, futureApplicationVersion, downloadServerAddresses, applicationServerAddresses, null));
}//if//
else {
Debug.log("Invalid license file: The application will not be registered.");
}//else//
}//registerApplication()//
/**
* ServerController constructor.
*/
protected ServerController() {
super();
}//ServerController()//
/* (non-Javadoc)
* @see com.foundation.view.swt.thin.controller.IThinServerController#registerClient(com.foundation.view.swt.thin.controller.IClientSessionController, String)
*/
public synchronized LiteHashMap registerClient(IHashMap registrationData) {
LiteHashMap result = null;
try {
String applicationBrowserVersion = (String) registrationData.get(REGISTRATION_APPLICATION_BROWSER_VERSION);
String applicationVersion = (String) registrationData.get(REGISTRATION_APPLICATION_VERSION);
IClientSessionController clientController = (IClientSessionController) registrationData.get(REGISTRATION_CLIENT_CONTROLLER);
String applicationName = (String) registrationData.get(REGISTRATION_APPLICATION_NAME);
ApplicationData applicationData = (ApplicationData) applicationMap.get(applicationName);
if(applicationData != null) {
LiteHashMap sessionData = new LiteHashMap(5);
boolean requiresApplicationBrowserUpdate = (applicationBrowserVersion == null) || (!applicationBrowserVersion.equals(applicationData.requiredApplicationBrowserVersion));
boolean requiresApplicationUpdate = (applicationVersion == null) || (!applicationVersion.equals(applicationData.requiredApplicationVersion));
LiteList downloadServerAddresses = null;
LiteList applicationServerAddresses = null;
try {
Monitor.lock(applicationData.downloadServerAddresses.getMonitor());
downloadServerAddresses = new LiteList(applicationData.downloadServerAddresses.toArray());
}//try//
finally {
Monitor.unlock(applicationData.downloadServerAddresses.getMonitor());
}//finally//
try {
Monitor.lock(applicationData.applicationServerAddresses.getMonitor());
applicationServerAddresses = new LiteList(applicationData.applicationServerAddresses.toArray());
}//try//
finally {
Monitor.unlock(applicationData.applicationServerAddresses.getMonitor());
}//finally//
//The signed license data that will verify the initial server addresses and validate the servers authenticity.//
//sessionData.put(SESSION_DATA_LICENSE, applicationData.license);
//The download server addresses.//
sessionData.put(SESSION_DATA_DOWNLOAD_SERVERS, downloadServerAddresses);
//The application server addresses.//
sessionData.put(SESSION_DATA_APPLICATION_SERVERS, applicationServerAddresses);
//If the client application version is not correct then tell the client to get the new version, else setup a session for the client.//
if(requiresApplicationBrowserUpdate || requiresApplicationUpdate) {
if(requiresApplicationBrowserUpdate) {
//Indicate that the application browser version is not correct for this application.//
sessionData.put(SESSION_DATA_REQUIRED_APPLICATION_BROWSER_VERSION, applicationData.requiredApplicationBrowserVersion);
}//if//
if(requiresApplicationUpdate) {
//Indicate that the application version is not correct for this application.//
sessionData.put(SESSION_DATA_REQUIRED_APPLICATION_VERSION, applicationData.requiredApplicationVersion);
}//if//
}//if//
else {
long sessionNumber = nextClientNumber++;
IServerSessionController serverSessionController = null;
//Setup the server side session controller.//
serverSessionController = new SessionController(this, getResourceService(), clientController, sessionNumber, applicationData.initialViewFactory, applicationData.codeDirectory);
sessionControllerMap.put(sessionNumber, serverSessionController);
//This number can be used by the client when reconnecting.//
sessionData.put(SESSION_DATA_CLIENT_NUMBER, new Long(sessionNumber));
//The server side session controller proxy.//
sessionData.put(SESSION_DATA_SERVER_SESSION_CONTROLLER, Orb.getProxy(serverSessionController, IServerSessionController.class));
//Add the future update for the application browser, and application.//
sessionData.put(SESSION_DATA_FUTURE_APPLICATION_BROWSER_VERSION, applicationData.futureApplicationBrowserVersion);
sessionData.put(SESSION_DATA_FUTURE_APPLICATION_VERSION, applicationData.futureApplicationVersion);
//Add the resource data and the code data.//
if(getResourceService() != null) {
sessionData.put(SESSION_DATA_RESOURCE_PACKAGE_DATA, getResourceService().getResourcePackageData());
}//if//
sessionData.put(SESSION_DATA_CODE_DATA, applicationData.codeMetadata);
}//else//
result = sessionData;
}//if//
else {
//TODO: Notify the client that the application does not exist, or is not being served by this server at this time.//
}//else//
}//try//
catch(Throwable e) {
Debug.log(e);
}//catch//
return result;
}//registerClient()//
/**
* Gets the resource service associated with this server controller.
* @return The related resource service.
*/
protected abstract AbstractResourceService getResourceService();
/* (non-Javadoc)
* @see com.foundation.view.swt.thin.controller.IThinServerController#unregisterClient(int)
*/
synchronized void unregisterClient(long sessionNumber) {
sessionControllerMap.remove(sessionNumber);
}//unregisterClient()//
/**
* Closes all open connections and cleans up.
*/
public synchronized void shutdown() {
IIterator iterator = sessionControllerMap.valueIterator();
IList sessionControllers = new LiteList(sessionControllerMap.getSize());
//TODO: There should be a convience method for this in the map.//
//Create a list of the session controllers.//
while(iterator.hasNext()) {
sessionControllers.add(iterator.next());
}//while//
iterator = sessionControllers.iterator();
while(iterator.hasNext()) {
((SessionController) iterator.next()).shutdown();
}//while//
applicationMap.removeAll();
applicationMap = null;
}//shutdown()//
/* (non-Javadoc)
* @see com.foundation.tcv.controller.IThinServerController#verifyClientVersion(java.lang.String)
*
public String[] verifyClientVersion(String version) {
return null;
}//verifyClientVersion()//
/* (non-Javadoc)
* @see com.foundation.tcv.controller.IThinServerController#getClientDownloadChunk(java.lang.String, int)
*/
public byte[] getClientDownloadChunk(String version, int previouslyDownloadedByteCount) {
return null;
}//getClientDownloadChunk()//
/* (non-Javadoc)
* @see com.foundation.tcv.controller.IThinServerController#getClientDownloadSize(java.lang.String)
*/
public int getClientDownloadSize(String version) {
return 0;
}//getClientDownloadSize()//
}//ServerController//

View File

@@ -0,0 +1,481 @@
/*
* Copyright (c) 2003,2009 Declarative Engineering LLC.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Declarative Engineering LLC
* verson 1 which accompanies this distribution, and is available at
* http://declarativeengineering.com/legal/DE_Developer_License_v1.txt
*/
package com.foundation.tcv.server.controller;
import java.io.File;
import com.common.orb.*;
import com.common.thread.IRunnable;
import com.common.util.*;
import com.common.util.optimized.*;
import com.common.comparison.Comparator;
import com.common.debug.*;
import com.common.exception.ThreadException;
import com.common.io.FileSupport;
import com.foundation.controller.*;
import com.foundation.view.IViewRequestHandler;
import com.foundation.view.ViewSystemMetadata;
import com.foundation.view.IRemoteSessionContext;
import com.foundation.view.IRemoteViewContext;
import com.foundation.view.resource.*;
import com.foundation.tcv.controller.*;
import com.foundation.tcv.server.util.EventLoop;
import com.foundation.tcv.view.ViewMessage;
/*
* Implements a server side controller of a client session. This controller manages a single client session in which there can be many open views.
*/
public class SessionController implements IRemoteSessionContext, IServerSessionController {
public static final boolean DEBUG = false;
/** A mapping of session view controllers indexed by the view number. */
private LongObjectHashMap sessionViewControllerMap = new LongObjectHashMap(20);
/** The next usable view number for a session view. */
private long nextSessionViewNumber = 1;
/** The remote client session proxy. */
private IClientSessionController clientController = null;
/** The controller that manages all the client/server sessions. */
private ServerController serverController = null;
/** This session's unique number. */
private long sessionNumber = 0L;
/** The next usable ordering number for server to client messages. This prevents the client from executing messages out of order. */
private long nextMessageOrderNumber = 0L;
/** The next expected number for client to server messages. This prevents this controller from executing messages out of order. */
private long nextRemoteMessageOrderNumber = 0L;
/** The view factory which will create the initial view for a client. */
private IInitialViewFactory initialViewFactory = null;
/** The event loop for this client/server session. Since all view systems are single threaded, all access must go through this event system object. */
private EventLoop eventLoop = null;
/** Determines whether the session has been shutdown. If this flag is true then all messages should terminate gracefully. */
private volatile boolean isShutdown = false;
/** An application data map that allows the application to store key value pairs associated with the view context. */
private LiteHashMap applicationDataMap = null;
/** The application specific context data. */
private Object applicationData = null;
/** The resource service from which the session can obtain the resource metadata to send to the client. */
private AbstractResourceService resourceService = null;
/** The directory where addition code is located for use by the client. */
private File codeDirectory = null;
/**
* SessionController constructor.
*/
public SessionController(ServerController serverController, AbstractResourceService resourceService, IClientSessionController clientController, long sessionNumber, IInitialViewFactory initialViewFactory, File codeDirectory) {
super();
this.serverController = serverController;
this.resourceService = resourceService;
this.clientController = clientController;
this.sessionNumber = sessionNumber;
this.initialViewFactory = initialViewFactory;
this.eventLoop = new EventLoop();
this.codeDirectory = codeDirectory;
//Register to be notified if the client socket is disconnected.//
Orb.registerInvalidProxyListener(clientController, new IInvalidProxyListener() {
public void evaluate(Object proxy) {
try {
getEventLoop().executeAsync(new IRunnable() {
public Object run() {
shutdown(); //TODO: Allow some time to reconnect?
return null;
}//run()//
});
}//try//
catch(ThreadException e) {
//Ignore - only occurs while shutting down if the event loop is shutdown before we get here.//
}//catch//
}//evaluate()//
});
}//SessionController()//
/**
* Gets the session number.
* @return The number identifying the session.
*/
public long getNumber() {
return sessionNumber;
}//getNumber()//
/**
* Gets the server controller which provides control over all client sessions and views.
* @return The server controller that accepted the client connection.
*/
public ServerController getServerController() {
return serverController;
}//getServerController()//
/**
* Gets the session view controller given the view number.
* @param sessionViewNumber The number assigned to the view.
* @return The session view controller for the view.
*/
public synchronized SessionViewController getSessionViewController(long sessionViewNumber) {
return (SessionViewController) sessionViewControllerMap.get(sessionViewNumber);
}//getSessionViewController()//
/**
* Gets the read only request handler reference used by the session to manage threading and message processing.
* @return The handler that manages messaging within the session.
*/
public EventLoop getViewRequestHandler() {
return eventLoop;
}//getViewRequestHandler()//
/* (non-Javadoc)
* @see com.foundation.view.IViewContext#getRequestHandler()
*/
public IViewRequestHandler getRequestHandler() {
return getViewRequestHandler();
}//getRequestHandler()//
/* (non-Javadoc)
* @see com.foundation.view.IViewContext#getResourceService()
*/
public AbstractResourceService getResourceService() {
return resourceService;
}//getResourceService()//
/* (non-Javadoc)
* @see com.foundation.view.IViewContext#getApplicationData()
*/
public Object getApplicationData() {
return applicationData;
}//getApplicationData()//
/* (non-Javadoc)
* @see com.foundation.view.IViewContext#setApplicationData(java.lang.Object)
*/
public void setApplicationData(Object applicationData) {
this.applicationData = applicationData;
}//setApplicationData()//
/* (non-Javadoc)
* @see com.foundation.view.IViewContext#getApplicationData(java.lang.Object)
*/
public Object getApplicationData(Object key) {
return applicationDataMap == null ? null : applicationDataMap.get(key);
}//getApplicationData()//
/* (non-Javadoc)
* @see com.foundation.view.IViewContext#setApplicationData(java.lang.Object, java.lang.Object)
*/
public void setApplicationData(Object key, Object applicationData) {
if(applicationDataMap == null) {
applicationDataMap = new LiteHashMap(10, Comparator.getLogicalComparator(), Comparator.getLogicalComparator());
}//if//
applicationDataMap.put(key, applicationData);
}//setApplicationData()//
/**
* Creates a new session view controller.
* @param viewController The view controller requiring the new session view context.
* @return A new session view controller that can be associated with a new view.
*/
public synchronized SessionViewController createSessionViewController(AbstractViewController viewController) {
long sessionViewNumber = nextSessionViewNumber++;
SessionViewController sessionViewController = null;
if(!(viewController instanceof RemoteViewController)) {
//TODO: Throw a better exception. See where this exception will normally be caught.
throw new RuntimeException("May not get a remote view context using a standard view controller. The view controller must extend RemoteViewController.");
}//if//
//Create a new session view controller.//
sessionViewController = new SessionViewController(this, sessionViewNumber, (RemoteViewController) viewController);
sessionViewControllerMap.put(sessionViewNumber, sessionViewController);
//Create the view on the client.//
clientController.createView(sessionViewNumber);
return sessionViewController;
}//createSessionViewController()//
/**
* Releases a view from memory. This should be called by the view controller after the view has been closed.
* @param sessionViewNumber The number of the view.
*/
public synchronized void releaseView(long sessionViewNumber) {
//Remove the view from the map.//
sessionViewControllerMap.remove(sessionViewNumber);
}//releaseView()//
/* (non-Javadoc)
* @see com.foundation.tcv.controller.IThinServerController#processMessages(long, long, com.common.util.IList, boolean)
*/
public Object processMessages(long messageOrderNumber, long sessionViewNumber, IList viewMessages, boolean waitForResponse) {
SessionViewController sessionViewController = null;
Object result = null;
boolean incrementedMessageNumber = false;
//Ensure that the messages get processed in the proper order.//
orderMessage(messageOrderNumber);
try {
//Get the session view controller for the messages.//
sessionViewController = getSessionViewController(sessionViewNumber);
//Ignore messages if the session view controller is null. It will only be null if the view is closed.//
if(sessionViewController != null) {
if(DEBUG) {
Debug.log("Receiving Messages: (thread: " + Thread.currentThread() + ")");
for(int index = 0; index < viewMessages.getSize(); index++) {
ViewMessage next = (ViewMessage) viewMessages.get(index);
Debug.log(" Msg #: " + next.getMessageNumber() + " Data: '" + next.getMessageData() + "'");
}//for//
}//if//
//Process the messages.//
eventLoop.processMessages(sessionViewController, viewMessages, waitForResponse);
//Increment the message order number after processing the messages to ensure that messages are processed in the order sent.//
incrementOrderNumber();
incrementedMessageNumber = true;
//Wait for the result of the last message (the last one is always going to be the round trip message).//
//Note: This must occur after incrementing the message order number so that future messages are processed.//
if(waitForResponse) {
ViewMessage last = (ViewMessage) viewMessages.getLast();
last.waitForResult(0);
result = last.getResult();
}//if//
}//if//
else {
Debug.log(new RuntimeException("View has already closed, the message could not be processed. This should never occur."));
}//else//
}//try//
finally {
if(!incrementedMessageNumber) {
incrementOrderNumber();
}//if//
}//finally//
return result;
}//processMessages()//
/**
* Halts this thread until all messages occuring before this one have started processing (are queued for processing).
* <p>Note: incrementOrderNumber() must be called after queuing the message(s) so that future messages don't wait idenfinatly.</p>
* @param messageOrderNumber The order number assigned to the message(s) to be processed.
* @see #incrementOrderNumber()
*/
private synchronized void orderMessage(long messageOrderNumber) {
//Make sure we process messages in order.//
if(nextRemoteMessageOrderNumber != messageOrderNumber) {
//Wait as long as we have not shutdown and this is not the next block of messages to process.//
while((serverController != null) && (nextRemoteMessageOrderNumber != messageOrderNumber)) {
try {
wait(1000);
}//try//
catch(Throwable e) {
Debug.handle(e);
}//catch//
}//while//
}//if//
}//orderMessage()//
/**
* Increments the order number after queuing the messages for processing.
*/
private synchronized void incrementOrderNumber() {
//Increment the next message order number so that other threads holding incomming messages can process them.//
nextRemoteMessageOrderNumber++;
}//incrementOrderNumber()//
/**
* Gets the event loop associated with this session controller.
* @return The session's event loop.
*/
public EventLoop getEventLoop() {
return eventLoop;
}//getEventLoop()//
/**
* Shuts down the entire session and closes all session views.
* <p>TODO: Should we allow a flag of some sort to notify view controllers that they should save data so that they can be reconnected to (or even serialized for later reconnection?).</p>
*/
public void shutdown() {
IList openViews = null;
if(!isShutdown) {
IIterator iterator = null;
synchronized(this) {
iterator = sessionViewControllerMap.valueIterator();
openViews = new LiteList(sessionViewControllerMap.getSize());
//Save all open view references so they are properly closed.//
while(iterator.hasNext()) {
openViews.add(iterator.next());
}//while//
this.clientController = null;
this.sessionViewControllerMap.removeAll();
//Set the shutdown flag.//
this.isShutdown = true;
}//synchronized//
//TODO: Should we notify users that the server is shutting down? Can we move client sessions to a backup or peer server?
//Request the session view controllers close.//
iterator = openViews.iterator();
while(iterator.hasNext()) {
//This should only occur if the client disconnected prematurly.//
((SessionViewController) iterator.next()).forcedClose();
}//while//
//TODO: Should probably unregister the invalid proxy handler, though it is not strictly necessary I guess.
//Cleanup.//
serverController.unregisterClient(sessionNumber);
}//if//
}//shutdown()//
/* (non-Javadoc)
* @see com.foundation.view.IRemoteSessionContext#createViewContext(com.foundation.controller.AbstractViewController)
*/
public IRemoteViewContext createViewContext(AbstractViewController viewController) {
return createSessionViewController(viewController);
}//createViewContext()//
/* (non-Javadoc)
* @see com.foundation.view.swt.thin.controller.IThinServerController#requestInitialView()
*/
public void requestInitialView() { //TODO: Allow the client to send some login info for auto & re-logins?
getRequestHandler().execute(new IRunnable() {
public Object run() {
RemoteViewController sessionViewController = initialViewFactory.createInitialView(SessionController.this);
//Display the initial view.//
sessionViewController.open();
return null;
}//run()//
}, true);
}//requestInitialView()//
/* (non-Javadoc)
* @see com.foundation.tcv.controller.IServerSessionController#requestResourceGroupData(com.common.util.IList)
*/
public IList requestResourceGroupData(IList resourcePackages) {
IList result = new LiteList(resourcePackages.getSize(), 10);
for(int packageIndex = 0; packageIndex < resourcePackages.getSize(); packageIndex++) {
//Use the resource service to access the resource group data to be sent to the client.//
result.add(resourceService.getResourceGroupData((ResourcePackage) resourcePackages.get(packageIndex)));
}//for//
return result;
}//requestResourceGroupData()//
/* (non-Javadoc)
* @see com.foundation.tcv.controller.IServerSessionController#requestResourceData(com.common.util.IList)
*/
public IList requestResourceData(IList resourceCategories) {
IList result = new LiteList(resourceCategories.getSize(), 10);
for(int categoryIndex = 0; categoryIndex < resourceCategories.getSize(); categoryIndex++) {
//Use the resource service to access the resource data to be sent to the client.//
result.add(resourceService.getResourceData((ResourceCategory) resourceCategories.get(categoryIndex)));
}//for//
return result;
}//requestResourceData()//
/* (non-Javadoc)
* @see com.foundation.tcv.controller.IServerSessionController#requestCodeData(java.lang.String[])
*/
public byte[][] requestCodeData(String[] codeNames) {
byte[][] result = new byte[codeNames.length][];
for(int index = 0; index < codeNames.length; index++) {
File codeFile = new File(codeDirectory, codeNames[index]);
if(codeFile.exists() && codeFile.isFile() && codeFile.canRead()) {
result[index] = FileSupport.getFileContents(codeFile);
}//if//
else {
Debug.log("Error: Requested code archive not available: " + codeNames[index]);
}//else//
}//for//
return result;
}//requestCodeData()//
/* (non-Javadoc)
* @see com.foundation.tcv.controller.IThinServerSession#closeSession()
*/
public synchronized void closeSession() {
serverController.unregisterClient(sessionNumber);
shutdown();
}//closeSession()//
/**
* Sends a collection messages to the client.
* @param sessionViewNumber The number of the view sending the messages.
* @param messages The collection of messages to be sent.
* @param resultCallback The callback that will handle processing the result of the last queued message. If this is null then the messages are one-way messages.
* @param hasTwoWayMessageContext Whether the message is being sent as a result of a two-way message initiated by the client.
* @return The message result, or null if the message is not synchronous.
*/
public Object sendMessages(long sessionViewNumber, IList messages, boolean isSynchronous, boolean hasTwoWayMessageContext) {
Object result = null;
long messageOrderNumber;
IClientSessionController clientSessionController = null;
synchronized(this) {
messageOrderNumber = nextMessageOrderNumber++;
clientSessionController = this.clientController;
}//synchronized//
try {
EventLoop.EventThread eventThread = null;
//If this is a one way call then notify the orb so we don't have to wait.//
if(!isSynchronous) {
Orb.setOneWayCall(clientSessionController);
}//if//
//If we are the event thread then we must reliquish our title until the remote call returns.//
if((isSynchronous) && (getEventLoop().isRequestThread())) {
eventThread = getEventLoop().pauseEventProcessing();
}//if//
try {
if(clientSessionController != null) {
//Make the remote call and get the result.//
result = clientSessionController.processMessages(messageOrderNumber, sessionViewNumber, messages, isSynchronous, hasTwoWayMessageContext);
}//if//
}//try//
catch(com.de22.orb.exception.InvalidProxyException e) {
//TODO: Should we just ignore this?
Debug.log(e, "This exception can probably be ignored..");
}//catch//
//If we were the event thread then become it again.//
if(eventThread != null) {
//Prevent errors in the client/server connection from deadlocking threads by checking for and handling a null result.//
if(result != null) {
long returnMessageNumber = ((com.foundation.tcv.view.OrderedResult) result).getMessageNumber();
result = ((com.foundation.tcv.view.OrderedResult) result).getResult();
//Process the result of the message in proper order so that messages sent before returning are processed before processing this result.//
orderMessage(returnMessageNumber);
//Add ourselves to the queue to as an event to become the event thread again.//
eventThread.requestResumeEventProcessing();
//Increment the order number such that future messages get processed.//
incrementOrderNumber();
//Wait to become the event thread again.//
eventThread.waitToResumeEventProcessing();
}//if//
else {
//Add ourselves to the queue to as an event to become the event thread again.//
eventThread.requestResumeEventProcessing();
//Wait to become the event thread again.//
eventThread.waitToResumeEventProcessing();
}//else//
}//if//
else if(result != null) {
result = ((com.foundation.tcv.view.OrderedResult) result).getResult();
}//else if//
}//try//
catch(Throwable e) {
if(Orb.isInvalidProxyException(e)) {
//TODO: Wait for a new proxy?
}//if//
else {
Debug.log(e, "This exception is likely to have occured on the client. The session is unlikely to recover from this exception.");
//TODO: What to do if there was some exception on the client?
}//else//
}//catch//
return result;
}//sendMessage()//
/* (non-Javadoc)
* @see com.foundation.view.IViewContext#getViewSystemMetadata()
*/
public ViewSystemMetadata getViewSystemMetadata() {
return clientController != null ? clientController.getViewSystemMetadata() : null;
}//getViewSystemMetadata()//
}//SessionController//

View File

@@ -0,0 +1,466 @@
/*
* Copyright (c) 2005,2009 Declarative Engineering LLC.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Declarative Engineering LLC
* verson 1 which accompanies this distribution, and is available at
* http://declarativeengineering.com/legal/DE_Developer_License_v1.txt
*/
package com.foundation.tcv.server.controller;
import com.common.util.*;
import com.common.debug.*;
import com.common.orb.Orb;
import com.common.thread.*;
import com.common.util.optimized.*;
import com.foundation.controller.*;
import com.foundation.tcv.server.util.*;
import com.foundation.tcv.view.*;
import com.foundation.tcv.controller.*;
import com.foundation.view.IRemoteSessionContext;
/*
* Manages the session for a perticular view. Each view in the session will create one instance of the session view controller.
*/
public class SessionViewController implements com.foundation.view.IRemoteViewContext, ISessionViewController {
/** The mapping of server side components indexed by their component number. */
private IntObjectHashMap componentMap = new IntObjectHashMap(20);
/** The next available component number to be used for relating components on the client with components on the server. */
private int nextComponentNumber = 1;
/** A queue of messages waiting to be sent to the client. */
private LiteList outgoingMessageQueue = new LiteList(100);
/** The number of holds placed on outgoing messages. */
private int messageHoldCount = 0;
/** Will be non-null as long as the view is not yet released. */
private SessionController sessionController = null;
/** The number the view is known by. */
private long sessionViewNumber = 0L;
/** Will be non-null as long as the view is not yet released. */
private RemoteViewController viewController = null;
/** Whether the session view controller has started or finished the process of shutting down. */
private volatile boolean isShutdown = false;
/** Tracks the number of incomplete two-way messages being processed. If this value is greater than zero then the event loop thread will only process messages initiated by its self, or via the Orb. Values greater than 1 indicate that nested 2-way calls are occuring. */
private int twoWayMessageCounter = 0;
/** A task to send the queued messages. This is only used if the messages are not high priority and if there may be more messages in the very near future. */
private Runnable sendMessageTask = null;
/**
* SessionViewController constructor.
*/
public SessionViewController(SessionController sessionController, long sessionViewNumber, RemoteViewController viewController) {
super();
this.sessionController = sessionController;
this.sessionViewNumber = sessionViewNumber;
this.viewController = viewController;
}//SessionViewController()//
/**
* Gets the session view number.
* @return The number identifying the view.
*/
public long getNumber() {
return sessionViewNumber;
}//getNumber()//
/**
* Gets the session controller for this view.
* @return The view's session which defines which client the view is displayed on.
*/
public SessionController getSessionController() {
return sessionController;
}//getSessionController()//
/**
* Gets the read only request handler reference used by the view to manage threading and message processing.
* @return The handler that manages messaging within the view.
*/
public EventLoop getViewRequestHandler() {
return getSessionController().getViewRequestHandler();
}//getViewRequestHandler()//
/**
* Registers a component in the view.
* @param component The component to be registered.
* @return The component's assigned number which can be used to communicate with the client side component.
*/
public synchronized int registerComponent(Object component) {
int number = nextComponentNumber++;
componentMap.put(number, component);
return number;
}//registerComponent()//
/**
* Gets the view component with the given number.
* @param componentNumber The view unique number for the desired component.
* @return The component with the assigned number.
*/
public synchronized Object getComponent(int componentNumber) {
return componentMap.get(componentNumber);
}//getComponent()//
/**
* Closes the view on the server and releases it.
* <p><b>Only the view controller should call this method.</b></p>
* @see #closeView()
*/
public void close() {
boolean releaseView = false;
synchronized(this) {
if(!isShutdown) {
if(sessionController != null) {
//TODO: Removing this code since it ignores queued messages waiting to be sent to the client. This could cause problems when closing the view from the server though.
//Notify the client that the view is closed.//
//sessionController.sendMessages(sessionViewNumber, new LiteList(new com.foundation.tcv.view.ViewMessage(0, IAbstractViewComponent.MESSAGE_CLOSE_VIEW, null, true)), false, true);
sendMessage(new com.foundation.tcv.view.ViewMessage(0, IAbstractRemoteViewComponent.MESSAGE_CLOSE_VIEW, null, null, 0, 0, true), false);
}//if//
this.viewController = null;
this.componentMap.removeAll();
this.isShutdown = true;
releaseView = true;
}//if//
}//synchronized//
if(releaseView) {
//Release the view.//
sessionController.releaseView(sessionViewNumber);
}//if//
}//close()//
/**
* Closes the view by notifying the primary view controller that it should close.
* TODO: Can we merge the two close methods?
*/
protected void forcedClose() {
boolean releaseView = false;
RemoteViewController viewController = null;
synchronized(this) {
if(!isShutdown) {
viewController = this.viewController;
this.viewController = null;
this.componentMap.removeAll();
this.isShutdown = true;
releaseView = true;
}//if//
}//synchronized//
if(releaseView) {
if(viewController != null) {
viewController.close();
}//if//
//Release the view.//
sessionController.releaseView(sessionViewNumber);
}//if//
}//forcedClose()//
/* (non-Javadoc)
* @see com.foundation.view.IRemoteViewContext#isValid()
*/
public synchronized boolean isValid() {
return sessionController != null;
}//isValid()//
/* (non-Javadoc)
* @see com.foundation.view.IRemoteViewContext#getSessionContext()
*/
public IRemoteSessionContext getSessionContext() {
return sessionController;
}//getSessionContext()//
/**
* Increments the hold count on the message queue so that messages will be queued up until the count reaches zero.
*/
public synchronized void incrementMessageHoldCount() {
messageHoldCount++;
// Debug.log("(" + sessionViewNumber + ") Incremented Hold Count | now: " + messageHoldCount);
}//incrementMessageHoldCount()//
/**
* Decrements the hold count on the message queue so that messages will be queued up until the count reaches zero.
*/
public synchronized void decrementMessageHoldCount() {
messageHoldCount--;
// Debug.log("(" + sessionViewNumber + ") Decrmented Hold Count | now: " + messageHoldCount);
if((messageHoldCount == 0) && (outgoingMessageQueue.getSize() > 0)) {
sendMessage(null, false, true);
}//if//
}//decrementMessageHoldCount()//
/**
* Sends a message to the client.
* @param viewMessage The message to be sent.
* @param isSynchronous Whether the message is synchronous (requiring a response). Synchronous messages are sent without delay and are unaffected by holds.
* @return Sends a message to the client and returns the response to the message if the message is fully synchronous.
*/
public Object sendMessage(ViewMessage viewMessage, boolean isSynchronous) {
return sendMessage(viewMessage, isSynchronous, isSynchronous);
}//sendMessage()//
/**
* Sends a message to the client.
* @param viewMessage The message to be sent.
* @param isSynchronous Whether the message is synchronous (requiring a response). Synchronous messages are sent without delay and are unaffected by holds.
* @param sendImmediately Whether the messages should be sent inlined versus waiting a very short time for additional messages.
* @return Sends a message to the client and returns the response to the message if the message is fully synchronous.
*/
public Object sendMessage(ViewMessage viewMessage, boolean isSynchronous, boolean sendImmediately) {
Object result = null;
IList messages = null;
synchronized(this) {
//The view message may be null if the component could not create one due to the view being in the process of shutting down. Gracefully ignore the null message.//
if(viewMessage != null) {
//If the message being sent is the view close message then clear any unnecessary pending messages since they are irrelevant.//
if(viewMessage.getControlNumber() == 0 && viewMessage.getMessageNumber() == IAbstractRemoteViewComponent.MESSAGE_CLOSE_VIEW) {
for(int index = outgoingMessageQueue.getSize() - 1; index >= 0; index--) {
ViewMessage next = (ViewMessage) outgoingMessageQueue.get(index);
//Keep messages sent to either the view controller on the client, or to the view's primary component since most of these messages are likely necessary (eg: release, and set visible messages).//
if(!(next.getControlNumber() == 0 || next.getControlNumber() == 1)) {
outgoingMessageQueue.remove(index);
}//if//
}//while//
}//if//
outgoingMessageQueue.add(viewMessage);
}//if//
//Create a list of messages to be sent if this is a synchronous message.//
if((isSynchronous) && (outgoingMessageQueue.getSize() > 0)) {
//Create a list of the queued messages.//
messages = new LiteList((ICollection) outgoingMessageQueue);
outgoingMessageQueue.removeAll();
}//if//
else if((twoWayMessageCounter == 0) && (sendMessageTask == null)) {
//If a message hold is in place then don't send now.//
if(messageHoldCount == 0) {
if(!sendImmediately) {
//Create a task to send queued messages to the client in a quarter second.//
sendMessageTask = new Runnable() {
public void run() {
LiteList messages = null;
//Make sure that this task is still valid and is not being superceeded.//
synchronized(SessionViewController.this) {
if(sendMessageTask == this) {
//Cleanup after the task.//
sendMessageTask = null;
//Create a list of the queued messages.//
messages = new LiteList((ICollection) outgoingMessageQueue);
outgoingMessageQueue.removeAll();
}//if//
}//synchronized//
if(messages != null) {
//Send the server the queued messages.//
sendMessages(messages, false);
}//if//
ActiveScheduler.getSingleton().remove(this);
}//run()//
};
ActiveScheduler.getSingleton().add(sendMessageTask, 50);
}//if//
else {
//Create a list of the queued messages.//
messages = new LiteList((ICollection) outgoingMessageQueue);
outgoingMessageQueue.removeAll();
}//else//
}//if//
}//else if//
}//synchronized()//
//Send the message now if we are not queuing messages or the message is not asynchronous.//
if(messages != null) {
result = sendMessages(messages, isSynchronous);
}//if//
return result;
}//sendMessage()//
/**
* Sends queued messages to the client.
* @param messages The messages to be sent to the client.
* @param isSynchronous Whether the call requires waiting for a result.
* @return Sends a message to the client and returns the response to the message if the message is fully synchronous.
*/
private Object sendMessages(IList messages, boolean isSynchronous) {
return sendMessages(messages, isSynchronous, twoWayMessageCounter > 0);
}//sendMessage()//
/**
* Sends queued messages to the client.
* @param messages The messages to be sent to the client.
* @param isSynchronous Whether the call requires waiting for a result.
* @param twoWayMessageContext Whether the messages are being sent under a two way message context, meaning the remote system sent a two way message and these are a result of the two way message.
* @return Sends a message to the client and returns the response to the message if the message is fully synchronous.
*/
private Object sendMessages(IList messages, boolean isSynchronous, boolean twoWayMessageContext) {
Object result = null;
SessionController sessionController = getSessionController();
if((messages.getSize() > 0) && (sessionController != null)) {
if(DEBUG) {
Debug.log("Sending Messages: (thread: " + Thread.currentThread() + ")");
for(int index = 0; index < messages.getSize(); index++) {
ViewMessage next = (ViewMessage) messages.get(index);
Debug.log(" Msg #: " + next.getMessageNumber() + " Data: '" + next.getMessageData() + "'");
}//for//
}//if//
//Send the queued messages to the client and return immediatly.//
result = sessionController.sendMessages(sessionViewNumber, messages, isSynchronous, twoWayMessageContext);
}//if//
return result;
}//sendMessage()//
/**
* Called by the session controller when it starts processing on a two way client initiated message.
*/
public synchronized void processingTwoWayStarted() {
twoWayMessageCounter++;
}//processingTwoWayStarted()//
/**
* Called by the session controller when it finishes processing on a two way client initiated message.
*/
public void processingTwoWayFinished() {
LiteList messages = null;
synchronized(this) {
twoWayMessageCounter--;
//Notify all waiting threads.//
if(twoWayMessageCounter == 0) {
if(outgoingMessageQueue.getSize() > 0) {
//Create a list of the queued messages.//
messages = new LiteList((ICollection) outgoingMessageQueue);
outgoingMessageQueue.removeAll();
}//if//
notifyAll();
}//if//
}//synchronized//
if(messages != null) {
//Send the server the queued messages.//
sendMessages(messages, false, true);
}//if//
}//processingTwoWayFinished()//
/* (non-Javadoc)
* @see com.foundation.view.IRemoteViewContext#execute(com.common.thread.IRunnable)
*/
public Object execute(IRunnable runnable) {
EventLoop eventLoop = getSessionController().getEventLoop();
//Note: This method is synchronized such that new 2-way messages will get added to the event queue after this request.//
//Block messages comming from the application as long as the thread is not the event thread and the server is processing a client initiated 2-way message associated with this view.//
if(!eventLoop.isRequestThread()) {
synchronized(this) { //TODO: Ideally this would be around the whole method, but the execute call at the bottom blocks which causes deadlocks. Find a way to ensure the event thread gets the runnable before a two-way message from the client.//
while(twoWayMessageCounter != 0) {
try {
wait(0);
}//try//
catch(InterruptedException e) {
Debug.handle(e);
}//catch//
}//while//
}//synchronized//
}//if//
return eventLoop.execute(runnable, true);
}//execute()//
/* (non-Javadoc)
* @see com.foundation.view.IRemoteViewContext#executeAsync(com.common.thread.IRunnable)
*/
public synchronized void executeAsync(final IRunnable runnable) {
final EventLoop eventLoop = getSessionController().getEventLoop();
//Note: This method is synchronized such that new 2-way messages will get added to the event queue after this request.//
//Avoid threading if the message can be immediatly added to the event queue.//
if((!eventLoop.isRequestThread()) && (twoWayMessageCounter != 0)) {
//Thread the adding of the asynch runnable so that the caller doesn't have to wait.//
ThreadService.run(new Runnable() {
public void run() {
synchronized(SessionViewController.this) {
//Block messages comming from the application as long as the thread is not the event thread and the server is processing a client initiated 2-way message associated with this view.//
while(twoWayMessageCounter != 0) {
try {
SessionViewController.this.wait(3000);
}//try//
catch(InterruptedException e) {
Debug.handle(e);
}//catch//
}//while//
eventLoop.execute(runnable, false);
}//synchronized//
}//run()//
});
}//if//
else {
eventLoop.execute(runnable, false);
}//else//
}//executeAsync()//
/* (non-Javadoc)
* @see com.foundation.view.IRemoteViewContext#remoteOpen(java.lang.String, java.lang.String, byte[])
*/
public void remoteOpen(String prefix, String extension, byte[] data) {
sendMessage(new ViewMessage(0, MESSAGE_OPEN_TEMP_FILE, new Object[] {extension, prefix, data}, null, 0, 0, false), false);
}//remoteOpen()//
/* (non-Javadoc)
* @see com.foundation.view.IRemoteViewContext#remoteLoad(java.lang.String[])
*/
public LoadData[] remoteLoad(String[] extensions, boolean allowMultiple) {
Object[][] data = (Object[][]) sendMessage(new ViewMessage(0, MESSAGE_LOAD_FROM_FILE, new Object[] {extensions, allowMultiple ? Boolean.TRUE : Boolean.FALSE}, null, 0, 0, false), true);
LoadData[] result = null;
if(data != null) {
result = new LoadData[data.length];
for(int index = 0; index < result.length; index++) {
result[index] = new LoadData((String) data[index][0], (byte[]) data[index][1]);
}//for//
}//if//
return result;
}//remoteLoad()//
/* (non-Javadoc)
* @see com.foundation.view.IRemoteViewContext#remoteSave(java.lang.String, byte[])
*/
public void remoteSave(String defaultName, byte[] data) {
sendMessage(new ViewMessage(0, MESSAGE_SAVE_TO_FILE, new Object[] {defaultName, data}, null, 0, 0, false), false);
}//remoteSave()//
/* (non-Javadoc)
* @see com.foundation.view.IRemoteViewContext#remoteStreamedSaveToFile(java.lang.String, java.lang.String, long, com.common.util.IContentStream)
*/
public String remoteStreamedSaveToFile(String defaultPath, String defaultName, long contentSize, IContentStream contentStream) {
return (String) sendMessage(new ViewMessage(0, MESSAGE_SAVE_TO_FILE_STREAMED, new Object[] {defaultPath, defaultName, new Long(contentSize)}, Orb.getProxy(contentStream, IContentStream.class), 0, 0, false), true);
}//remoteStreamedSaveToFile()//
/* (non-Javadoc)
* @see com.foundation.view.IRemoteViewContext#remoteStreamedLoadFromFile(java.lang.String[], boolean)
*/
public LoadStreamedData[] remoteStreamedLoadFromFile(String[] extensions, boolean allowMultiple) {
return remoteStreamedLoadFromFile(extensions, allowMultiple, null, null);
}//remoteStreamedLoadFromFile()//
/* (non-Javadoc)
* @see com.foundation.view.IRemoteViewContext#remoteStreamedLoadFromFile(java.lang.String[], boolean, java.lang.String, java.lang.String)
*/
public LoadStreamedData[] remoteStreamedLoadFromFile(String[] extensions, boolean allowMultiple, String fileName, String filterPath) {
return (LoadStreamedData[]) sendMessage(new ViewMessage(0, MESSAGE_LOAD_FROM_FILE_STREAMED, extensions, fileName == null && filterPath == null ? null : new Object[] {fileName, filterPath}, allowMultiple ? 1 : 0, 0, false), true);
}//remoteStreamedLoadFromFile()//
/* (non-Javadoc)
* @see com.foundation.view.IRemoteViewContext#remoteCollectFilesToRename(java.lang.String[], boolean, java.lang.String, java.lang.String)
*/
public String[] remoteCollectFilesToRename(String[] extensions, boolean allowMultiple, String fileName, String filterPath) {
return (String[]) sendMessage(new ViewMessage(0, MESSAGE_COLLECT_FILES_TO_RENAME, extensions, fileName == null && filterPath == null ? null : new Object[] {fileName, filterPath}, allowMultiple ? 1 : 0, 0, false), true);
}//remoteCollectFilesToRename()//
/* (non-Javadoc)
* @see com.foundation.view.IRemoteViewContext#remoteRenameFiles(java.lang.String[], boolean)
*/
public void remoteRenameFiles(String[] newFileNames, boolean includesPaths) {
sendMessage(new ViewMessage(0, MESSAGE_RENAME_FILES, newFileNames, null, includesPaths ? 1 : 0, 0, false), true);
}//remoteRenameFiles()//
/* (non-Javadoc)
* @see com.foundation.view.IRemoteViewContext#remoteSaveToFilesStreamed(java.lang.String, java.lang.String[], com.common.util.IContentStream[])
*/
public int remoteSaveToFilesStreamed(String defaultPath, String[] fileNames, IContentStream[] contentStreams) {
IContentStream[] streamProxies = new IContentStream[contentStreams.length];
for(int index = 0; index < contentStreams.length; index++) {
streamProxies[index] = (IContentStream) Orb.getProxy(contentStreams[index], IContentStream.class);
}//for//
return ((Integer) sendMessage(new ViewMessage(0, MESSAGE_SAVE_TO_FILES_STREAMED, new Object[] {defaultPath, fileNames}, streamProxies, 0, 0, false), true)).intValue();
}//remoteSaveToFilesStreamed()//
}//SessionViewController//

View File

@@ -0,0 +1,381 @@
/*
* Copyright (c) 2004,2009 Declarative Engineering LLC.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Declarative Engineering LLC
* verson 1 which accompanies this distribution, and is available at
* http://declarativeengineering.com/legal/DE_Developer_License_v1.txt
*/
package com.foundation.tcv.server.util;
import com.common.util.*;
import com.common.debug.*;
import com.common.thread.*;
import com.foundation.tcv.view.IAbstractRemoteViewComponent;
import com.foundation.tcv.view.ViewMessage;
import com.foundation.tcv.server.controller.SessionViewController;
import com.foundation.view.IViewRequestHandler;
/**
* Manages interaction with the system of objects in a view.
* The purpose is to ensure single threaded access to all view and view related objects (includes model, controller, and view controllers associated with the view).
* <p>The design is for only one thread (not necessarily a single thread) to interact with view related objects at one time.
* Some round trip calls to the client must be allowed, such as a view synchronize, which may require resulting messages from the client
* to be processed prior to the round trip call completing. In order to accomplish this, the round trip call must yeild access to view
* objects to another thread until the round trip complete message is received from the server, at which time the yeilding thread must
* take over all view access responsibilities and complete its call.</p>
* <p><b>This is the THIN client event loop for SWT.</b></p>
*/
public class EventLoop implements Runnable, IViewRequestHandler, ISingleThreadedContext {
/** The queue that all requests are sent to. Requests made on the event thread are handled immediatly and are never queued. */
private Queue queue = new Queue(5);
/** The thread currently handing requests. */
private Thread queueRunner = null;
/** Whether the loop is running. */
private boolean isRunning = false;
/**
* Maintains request information and handles notification of completion.
*/
private static final class Request implements Runnable {
private IRunnable runnable = null;
private Object returnValue = null;
private boolean requestComplete = false;
/**
* Request constructor.
*/
private Request(IRunnable runnable) {
this.runnable = runnable;
}//Request()//
public synchronized void waitForCompletion() {
while(!requestComplete) {
try {
wait(1000);
}//try//
catch(InterruptedException e) {
Debug.handle(e);
}//catch//
}//while//
}//waitForCompletion()//
public void run() {
try {
if(runnable != null) {
returnValue = runnable.run();
}//if//
else {
//TODO: Remove me.
//returnValue = component.processRequest(requestNumber, value1, value2, value3, value4);
Debug.log(new RuntimeException("Not used any more."));
}//else//
}//try//
catch(Throwable e) {
Debug.log("Failed to complete the processing of a remote server swt view request.", e);
}//catch//
synchronized(this) {
requestComplete = true;
notifyAll();
}//synchronized//
}//run()//
}//Request//
/**
* Encapsulates an event thread which has reliquished its status for a time while it performs some unrelated processing which may take an arbitrary amount of time.
*/
public class EventThread {
/** Whether the held thread is once again the event thread. */
private boolean isEventThread = false;
/** The thread which requested a pause in it being the event thread. */
private Thread eventThread = null;
/**
* EventThread constructor.
*/
public EventThread() {
this.eventThread = Thread.currentThread();
}//EventThread()//
/**
* Requests that the calling thread become the event thread once more.
*/
public void requestResumeEventProcessing() {
processRequest(this);
}//resumeEventProcessing()//
/**
* Waits for the current event thread to make us the event thread.
*/
public void waitToResumeEventProcessing() {
//A little bit of error checking.//
if(eventThread != Thread.currentThread()) {
throw new IllegalArgumentException("Only the former event thread may request it become the event thread again.");
}//if//
//Synchronize so we can wait to be called on.//
synchronized(EventLoop.this) {
//Wait while we have not been called on to become the event thread again.//
while(!isEventThread) {
try {
EventLoop.this.wait(3000);
}//try//
catch(InterruptedException e) {
Debug.handle(e);
}//catch//
}//while//
}//synchronized//
ThreadService.setIsInSingleThreadedContext(EventLoop.this);
}//waitToResumeEventProcessing()//
/**
* Called by the current event thread to yield event processing to the held thread.
* <p>Note: The caller must hold a synch lock on the EventLoop object.</p>
*/
private void notifyEventThread() {
//Let the proper thread know it is now the event thread.//
isEventThread = true;
//Assign the new queue runner to this thread.//
queueRunner = eventThread;
//Notify all waiting threads.//
EventLoop.this.notifyAll();
}//notifyEventThread()//
}//EventThread//
/**
* EventLoop constructor.
*/
public EventLoop() {
}//EventLoop()//
/**
* Runs the request handler.
* <p>The handler is designed to run until there are no more requests, then it will quit. When new requests arrive they will start the handler if it is not already running.</p>
*/
public void run() {
boolean stop = false;
boolean isEventThread = true;
ThreadService.setIsInSingleThreadedContext(this);
isRunning = true;
while((isEventThread) && (!stop)) {
try {
Object next = null;
do {
next = null;
//Get the next runnable.//
synchronized(this) {
if(queue.getSize() > 0) {
//Check the message queue.//
next = queue.dequeue();
}//if//
}//synchronized//
if(next != null) {
if(next instanceof EventThread) {
EventThread eventThread = (EventThread) next;
synchronized(this) {
//Notify the event thread that it may now take over as the event thread again.//
eventThread.notifyEventThread();
}//synchronized//
//Set the flag to immediatly quit processing messages as the event thread.//
isEventThread = false;
}//if//
else if(next instanceof ViewMessage) {
ViewMessage message = (ViewMessage) next;
SessionViewController sessionViewController = (SessionViewController) message.getExtra();
IAbstractRemoteViewComponent component = (IAbstractRemoteViewComponent) sessionViewController.getComponent(message.getControlNumber());
Object result = null;
if(message.isTwoWayMessage()) {
sessionViewController.processingTwoWayStarted();
}//if//
//Process the message and retreive the result.//
result = component.processMessage(message);
if(message.isTwoWayMessage()) {
sessionViewController.processingTwoWayFinished();
//Save the result for the waiting ORB thread.//
message.setResult(result);
}//if//
}//if//
else {
((Runnable) next).run();
}//else//
}//if//
} while((isEventThread) && (!stop) && (next != null));
if((isEventThread) && (!stop)) {
synchronized(this) {
if(queue.getSize() == 0) {
//Wait a very short time to see if new messages are queued or event threads ready to restart. If not then reclaim this thread.//
try {
wait(100);
}//try//
catch(Throwable e) {
Debug.handle(e);
}//catch//
if(queue.getSize() == 0) {
stop = true;
queueRunner = null;
}//if//
}//if//
}//synchronized//
}//if//
}//try//
catch(Throwable e) {
Debug.log(e);
//Keep processing messages so the client might not lock up.//
}//catch//
}//while//
ThreadService.setIsInSingleThreadedContext(null);
isRunning = false;
}//run()//
/**
* Pauses the calling thread such that it is no longer the event thread and may block without affecting the event processing system.
* When the calling thread is ready to resume being the event thread then it must call the returned EventThread object's resumeEventProcessing() method which will block until the calling thread is once again the event thread.
* <p>Warning: The EventThread object should never be passed to another thread since it will corrupt the EventLoop state.</p>
* @return The EventThread instance which is necessary for resuming the mantel of event thread when the thread has finished waiting.
*/
public EventThread pauseEventProcessing() {
EventThread result = new EventThread();
ThreadService.setIsInSingleThreadedContext(this);
synchronized(this) {
//Ensure that all know we are no longer the event thread.//
queueRunner = null;
//If necessary, start another thread to be the event thread.//
if(queue.getSize() != 0) {
queueRunner = ThreadService.run(this, false);
}//if//
}//synchronized//
return result;
}//pauseEventProcessing()//
/**
* Adds the messages to the process queue and if necessary starts a thread to process them in order.
* @param messages The ordered messages to be processed.
* @param waitForResponse Whether to wait for the result of the last message.
* @return The result of the last message, or null.
*/
public void processMessages(SessionViewController sessionViewController, IList messages, boolean waitForResponse) {
//Note: The queue runner thread will never be this thread since this thread is always comming from the ORB.//
synchronized(this) {
IIterator iterator = messages.iterator();
while(iterator.hasNext()) {
ViewMessage next = (ViewMessage) iterator.next();
next.setExtra(sessionViewController);
queue.enqueue(next);
}//while//
if(queueRunner == null) {
//Start the QueueRunner thread.//
queueRunner = ThreadService.run(this, false);
}//if//
}//synchronized//
}//processMessages()//
/**
* Processes a request through the event thread. This is required to access any SWT method due to the design of SWT.
* @param request The object that will be run when the request is processed.
* @param synchronous Whether the thread should block until the request has been processed (should be true if a result is desired).
*/
public void processRequest(final Runnable request, boolean synchronous) {
execute(new IRunnable() {
public Object run() {
request.run();
return null;
}//run()//
}, synchronous);
}//processRequest()//
/**
* Processes a request through the event thread.
* @param request The request to be processed.
*/
private synchronized void processRequest(Runnable request) {
queue.enqueue(request);
if(queueRunner == null) {
//Start the QueueRunner thread.//
queueRunner = ThreadService.run(this, false);
}//if//
}//processRequest()//
/**
* Processes a request through the event thread.
* @param request The request to be processed.
*/
private synchronized void processRequest(EventThread request) {
queue.enqueue(request);
if(queueRunner == null) {
//Start the QueueRunner thread.//
queueRunner = ThreadService.run(this, false);
}//if//
}//processRequest()//
/* (non-Javadoc)
* @see com.foundation.event.IRequestHandler#isRunning()
*/
public boolean isRunning() {
return isRunning;
}//isRunning()//
/* (non-Javadoc)
* @see com.foundation.event.IRequestHandler#isRequestThread()
*/
public boolean isRequestThread() {
Thread currentThread = Thread.currentThread();
//This synchronization is necessary in order to ensure the local processor cache is updated.//
synchronized(this) {
return queueRunner == currentThread;
}//synchronized//
}//isRequestThread()//
/* (non-Javadoc)
* @see com.foundation.event.IRequestHandler#execute(com.common.thread.IRunnable, boolean)
*/
public Object execute(IRunnable runnable, boolean synchronous) {
Object result = null;
boolean isQueueRunner = false;
Thread currentThread = Thread.currentThread();
//This synchronization is necessary in order to ensure the local processor cache is updated.//
synchronized(this) {
isQueueRunner = queueRunner == currentThread;
}//synchronized//
//Make sure we don't have the event thread creating requests since it would deadlock the system.//
if(!isQueueRunner || !synchronous) {
Request request = new Request(runnable);
processRequest(request);
if(synchronous) {
request.waitForCompletion();
result = request.returnValue;
}//if//
}//if//
else {
//The calling thread is the event thread, so there is no need to pass the request through the event thread!.//
result = runnable.run();
}//else//
return result;
}//execute()//
/* (non-Javadoc)
* @see com.foundation.event.IRequestHandler#execute(com.common.thread.IRunnable)
*/
public Object execute(IRunnable runnable) {
return execute(runnable, true);
}//execute()//
/* (non-Javadoc)
* @see com.foundation.event.IRequestHandler#executeAsync(com.common.thread.IRunnable)
*/
public void executeAsync(IRunnable runnable) {
execute(runnable, false);
}//executeAsync()//
}//RequestHandler//