Initial commit from SVN.
This commit is contained in:
11
Foundation TCV Client/.classpath
Normal file
11
Foundation TCV Client/.classpath
Normal 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="/Foundation"/>
|
||||
<classpathentry combineaccessrules="false" kind="src" path="/Class File Services"/>
|
||||
<classpathentry combineaccessrules="false" kind="src" path="/Orb"/>
|
||||
<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER"/>
|
||||
<classpathentry kind="output" path="bin"/>
|
||||
</classpath>
|
||||
20
Foundation TCV Client/.project
Normal file
20
Foundation TCV Client/.project
Normal file
@@ -0,0 +1,20 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<projectDescription>
|
||||
<name>Foundation TCV Client</name>
|
||||
<comment></comment>
|
||||
<projects>
|
||||
<project>Common</project>
|
||||
<project>Foundation</project>
|
||||
<project>Foundation TCV</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>
|
||||
@@ -0,0 +1,233 @@
|
||||
/*
|
||||
* 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.client.application;
|
||||
|
||||
import java.io.File;
|
||||
import com.common.util.*;
|
||||
import com.foundation.application.*;
|
||||
|
||||
public abstract class AbstractClientApplication extends Application {
|
||||
/** The one and only application instance. */
|
||||
private static AbstractClientApplication singleton = null;
|
||||
|
||||
/** The name of the application which is used to address the application on the server. */
|
||||
private String applicationName = null;
|
||||
/** The version string for the current application (not the application browser) which is used to download application specific resource updates. This can be null in development environments. */
|
||||
private String applicationVersion = null;
|
||||
/** The version string for the current application browser which is running. */
|
||||
private String applicationBrowserVersion = null;
|
||||
/** The collection of unordered Address instances used to connect with the application server(s). */
|
||||
private IList applicationAddresses = new LiteList(10, 10);
|
||||
/** The collection of unordered Address instances used to connect with the download server(s). */
|
||||
private IList downloadAddresses = new LiteList(10, 10);
|
||||
/** The installation directory for the application (not the application browser). */
|
||||
private File applicationBrowserDirectory = null;
|
||||
/** The installation directory for the application (not the application browser). */
|
||||
private File applicationDirectory = null;
|
||||
/** The directory containing the cached resources for the application. */
|
||||
private File applicationCacheDirectory = null;
|
||||
/** The file that we read the application data from. */
|
||||
private File applicationDataFile = null;
|
||||
/**
|
||||
* AbstractClientApplication constructor.
|
||||
*/
|
||||
public AbstractClientApplication() {
|
||||
super();
|
||||
}//AbstractClientApplication()//
|
||||
/**
|
||||
* Gets the one and only instance of the client application.
|
||||
* @return The single instance of this class.
|
||||
*/
|
||||
public static AbstractClientApplication getSingleton() {
|
||||
return singleton;
|
||||
}//getSingleton()//
|
||||
/**
|
||||
* Sets the one and only instance of the client application.
|
||||
* @param _singleton The single instance of this class.
|
||||
*/
|
||||
protected static void setSingleton(AbstractClientApplication _singleton) {
|
||||
singleton = _singleton;
|
||||
}//setSingleton()//
|
||||
/**
|
||||
* Gets the path to the resources defined by the server.
|
||||
* @return The path where the server defined resources are stored.
|
||||
*/
|
||||
public abstract File getRemoteResourcesPath();
|
||||
/**
|
||||
* Gets the begining part of the application file name. Append the version and the extension to get the full file name.
|
||||
* @return The first part of the client file name as a string.
|
||||
*/
|
||||
public abstract String getApplicationFileName();
|
||||
/**
|
||||
* Displays license information when the server application doesn't provide an application license.
|
||||
*/
|
||||
public abstract void displayNoLicenseSplash();
|
||||
/**
|
||||
* Displays license information when the server application doesn't provide a valid application license.
|
||||
*/
|
||||
public abstract void displayInvalidLicenseSplash();
|
||||
/**
|
||||
* The public key data required to validate applications.
|
||||
* @return The RSA public key data used for license validation.
|
||||
*/
|
||||
public abstract byte[][] getValidationData();
|
||||
/**
|
||||
* Downloads the requested client version. Currently the location data is ignored and the data is downloaded from the server directly.
|
||||
* @param serverController The server controller reference that will be used to download if an FTP/HTTP.
|
||||
* @param version The version of the client to be downloaded.
|
||||
* @param locationData Eventually used to identify FTP or HTTP servers that will manage the download.
|
||||
* @return The file name that should be invoked. An exception will be raised if the download was not completed or there was a problem storing the download.
|
||||
*/
|
||||
protected java.io.File downloadVersion(com.foundation.tcv.controller.IThinServerController serverController, String version, IList locationData) throws java.io.IOException {
|
||||
String applicationName = getApplicationName();
|
||||
java.io.FileOutputStream fout = null;
|
||||
java.io.File file = null;
|
||||
|
||||
//TODO: Display a dialog notifying the user that the download is occuring and animating so the user doesn't think the app froze.//
|
||||
try {
|
||||
java.io.File dir = new java.io.File(getApplicationDirectory(), version);
|
||||
int totalSize = serverController.getClientDownloadSize(version);
|
||||
int downloadedCount = file.exists() ? (int) file.length() : 0;
|
||||
byte[] chunk = null;
|
||||
|
||||
if(!dir.exists()) {
|
||||
if(!dir.mkdir()) {
|
||||
throw new java.io.IOException("Error: Unable to make the directory.");
|
||||
}//if//
|
||||
}//if//
|
||||
|
||||
file = new java.io.File(dir, applicationName);
|
||||
fout = new java.io.FileOutputStream(file.getAbsolutePath(), true);
|
||||
|
||||
while(downloadedCount != totalSize) {
|
||||
chunk = serverController.getClientDownloadChunk(version, downloadedCount);
|
||||
downloadedCount += chunk.length;
|
||||
fout.write(chunk);
|
||||
}//while//
|
||||
}//try//
|
||||
finally {
|
||||
if(fout != null) {
|
||||
fout.close();
|
||||
}//if//
|
||||
}//finally//
|
||||
|
||||
return file;
|
||||
}//downloadVersion()//
|
||||
/**
|
||||
* Gets the installation directory for the application browser.
|
||||
* @return The directory file where this version of the application browser is installed.
|
||||
*/
|
||||
public File getApplicationBrowserDirectory() {
|
||||
return applicationBrowserDirectory;
|
||||
}//getApplicationBrowserDirectory()//
|
||||
/**
|
||||
* Sets the installation directory for the application browser.
|
||||
* @param applicationBrowserDirectory The directory file where this version of the application browser is installed.
|
||||
*/
|
||||
protected void setApplicationBrowserDirectory(File applicationBrowserDirectory) {
|
||||
this.applicationBrowserDirectory = applicationBrowserDirectory;
|
||||
}//setApplicationBrowserDirectory()//
|
||||
/**
|
||||
* Gets the installation directory for the application (not the application browser).
|
||||
* @return The directory file where the current application is installed.
|
||||
*/
|
||||
public File getApplicationDirectory() {
|
||||
return applicationDirectory;
|
||||
}//getApplicationDirectory()//
|
||||
/**
|
||||
* Sets the installation directory for the application (not the application browser).
|
||||
* @param applicationDirectory The directory file where the current application is installed.
|
||||
*/
|
||||
protected void setApplicationDirectory(File applicationDirectory) {
|
||||
this.applicationDirectory = applicationDirectory;
|
||||
}//setApplicationDirectory()//
|
||||
/**
|
||||
* Gets the directory containing the cached resources for the application.
|
||||
* @return The directory file where resources are cached for the current application.
|
||||
*/
|
||||
public File getApplicationCacheDirectory() {
|
||||
return applicationCacheDirectory;
|
||||
}//getApplicationCacheDirectory()//
|
||||
/**
|
||||
* Sets the directory containing the cached resources for the application.
|
||||
* @param applicationCacheDirectory The directory file where resources are cached for the current application.
|
||||
*/
|
||||
protected void setApplicationCacheDirectory(File applicationCacheDirectory) {
|
||||
this.applicationCacheDirectory = applicationCacheDirectory;
|
||||
}//setApplicationCacheDirectory()//
|
||||
/**
|
||||
* Gets the file containing the application setup data.
|
||||
* @return The setup data file for this application. It specifies the application browser version to be used, the download and application server addresses, and the name of the application.
|
||||
*/
|
||||
public File getApplicationDataFile() {
|
||||
return applicationDataFile;
|
||||
}//getApplicationDataFile()//
|
||||
/**
|
||||
* Sets the file containing the application setup data.
|
||||
* @param applicationDataFile The setup data file for this application. It specifies the application browser version to be used, the download and application server addresses, and the name of the application.
|
||||
*/
|
||||
protected void setApplicationDataFile(File applicationDataFile) {
|
||||
this.applicationDataFile = applicationDataFile;
|
||||
}//setApplicationDataFile()//
|
||||
/**
|
||||
* Gets the collection of Address instances that the application browser can use to connect to the application.
|
||||
* @return The collection, in no perticular order, of application server addresses.
|
||||
*/
|
||||
protected IList getApplicationAddresses() {
|
||||
return applicationAddresses;
|
||||
}//getApplicationAddresses()//
|
||||
/**
|
||||
* Gets the collection of Address instances that the application browser can use to download newer versions of the application and application browser.
|
||||
* @return The collection, in no perticular order, of download server addresses for this application.
|
||||
*/
|
||||
protected IList getDownloadAddresses() {
|
||||
return downloadAddresses;
|
||||
}//getDownloadAddresses()//
|
||||
/**
|
||||
* Gets the version string for the application (not the application browser).
|
||||
* @return The application version which can be used to update application specific files (such as a dll containing graphics for the links).
|
||||
*/
|
||||
public String getApplicationVersion() {
|
||||
return applicationVersion;
|
||||
}//getApplicationVersion()//
|
||||
/**
|
||||
* Sets the version string for the application (not the application browser).
|
||||
* @param applicationVersion The application version which can be used to update application specific files (such as a dll containing graphics for the links).
|
||||
*/
|
||||
protected void setApplicationVersion(String applicationVersion) {
|
||||
this.applicationVersion = applicationVersion;
|
||||
}//setApplicationVersion()//
|
||||
/**
|
||||
* Gets the name of the requested application. Normally this is the company name : product name.
|
||||
* @return The name of the application.
|
||||
*/
|
||||
public String getApplicationName() {
|
||||
return applicationName;
|
||||
}//getApplicationName()//
|
||||
/**
|
||||
* Sets the name of the requested application. Normally this is the company name : product name.
|
||||
* @param applicationName The name of the application.
|
||||
*/
|
||||
protected void setApplicationName(String applicationName) {
|
||||
this.applicationName = applicationName;
|
||||
}//setApplicationName()//
|
||||
/**
|
||||
* Gets the version string identifying the current running version of the application browser.
|
||||
* @return The version string identifying this appliation browser's version.
|
||||
*/
|
||||
public String getApplicationBrowserVersion() {
|
||||
return applicationBrowserVersion;
|
||||
}//getApplicationBrowserVersion()//
|
||||
/**
|
||||
* Sets the version string identifying the current running version of the application browser.
|
||||
* @param applicationBrowserVersion The version string identifying this appliation browser's version.
|
||||
*/
|
||||
protected void setApplicationBrowserVersion(String applicationBrowserVersion) {
|
||||
this.applicationBrowserVersion = applicationBrowserVersion;
|
||||
}//setApplicationBrowserVersion()//
|
||||
}//AbstractClientApplication//
|
||||
@@ -0,0 +1,339 @@
|
||||
/*
|
||||
* Copyright (c) 2006,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.client.application;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.util.zip.GZIPInputStream;
|
||||
|
||||
import com.common.comparison.Comparator;
|
||||
import com.common.debug.Debug;
|
||||
import com.common.io.ByteArrayInputStream;
|
||||
import com.common.io.FileSupport;
|
||||
import com.common.io.ObjectInputStream;
|
||||
import com.common.security.Sha1;
|
||||
import com.common.util.*;
|
||||
import com.foundation.tcv.controller.IServerSessionController;
|
||||
import com.foundation.view.resource.AbstractResourceService;
|
||||
import com.foundation.view.resource.ResourceCategory;
|
||||
import com.foundation.view.resource.ResourceGroup;
|
||||
import com.foundation.view.resource.ResourcePackage;
|
||||
|
||||
/*
|
||||
* A specialized resource service that handles local and remote resources being merged.
|
||||
*/
|
||||
public class TcvResourceService extends AbstractResourceService {
|
||||
/** The application being serviced. */
|
||||
private AbstractClientApplication application;
|
||||
/** The set of all packages. */
|
||||
private IList resourcePackages;
|
||||
/** A mapping of ILists of ResourceGroups indexed by the ResourcePackage that defines them. */
|
||||
private IHashMap packageToGroupsMap = new LiteHashMap(10);
|
||||
/** The set of all categories defined by the application. */
|
||||
private IHashSet categories = new LiteHashSet(40);
|
||||
/** The set of all remote packages. */
|
||||
private IList remotePackages = null;
|
||||
/** The session controller used to request resource data. */
|
||||
private IServerSessionController sessionController = null;
|
||||
/**
|
||||
* TcvResourceService constructor.
|
||||
* @param application The application being serviced.
|
||||
* @throws IOException
|
||||
* @throws ClassNotFoundException
|
||||
*/
|
||||
public TcvResourceService(AbstractClientApplication application) {
|
||||
super(null);
|
||||
this.application = application;
|
||||
}//TcvResourceService()//
|
||||
/**
|
||||
* Validates that the resource group metadata for the local packages has not been tampered with.
|
||||
* @param localPackages The local package metadata.
|
||||
* @return Whether the resource group metadata on disk has been validated.
|
||||
*/
|
||||
protected boolean validateLocalGroups(IList localPackages) {
|
||||
boolean result = true;
|
||||
|
||||
for(int packageIndex = 0; (result) && (packageIndex < localPackages.getSize()); packageIndex++) {
|
||||
ResourcePackage resourcePackage = (ResourcePackage) localPackages.get(packageIndex);
|
||||
File path = new File(application.getResourcesPath(), resourcePackage.getPath());
|
||||
|
||||
if(path.exists() && path.isFile() && path.canRead()) {
|
||||
String fileHash = FileSupport.getFileHash(path, new Sha1());
|
||||
|
||||
if(!Comparator.equals(fileHash, resourcePackage.getHash())) {
|
||||
result = false;
|
||||
}//if//
|
||||
}//if//
|
||||
else {
|
||||
result = false;
|
||||
}//else//
|
||||
}//for//
|
||||
|
||||
return result;
|
||||
}//validateLocalGroups()//
|
||||
/**
|
||||
* Sets the remote resource package data and identifies any missing resource group metadata and deletes any that are stale.
|
||||
* @param packageData The remote packages defined by the application in a compressed serialized form.
|
||||
* @return The set of missing or stale ResourcePackage instances. This will be null if there were none.
|
||||
*/
|
||||
public IList setRemoteResourcePackageData(byte[] packageData) {
|
||||
LiteList result = null;
|
||||
|
||||
//TODO: Save the file locally. It could be useful if the app is capable of starting up without a connection to the server.
|
||||
|
||||
if(packageData != null) {
|
||||
ObjectInputStream in = null;
|
||||
GZIPInputStream zin = null;
|
||||
ByteArrayInputStream bin = null;
|
||||
|
||||
//Convert the compressed serialized collection of resource package data into the object model.//
|
||||
try {
|
||||
in = new ObjectInputStream(zin = new GZIPInputStream(bin = new ByteArrayInputStream(packageData)), null, null);
|
||||
|
||||
remotePackages = (IList) in.readObject();
|
||||
}//try//
|
||||
catch(Throwable e) {
|
||||
Debug.log(e);
|
||||
}//catch//
|
||||
finally {
|
||||
try {
|
||||
in.close();
|
||||
zin.close();
|
||||
bin.close();
|
||||
}//try//
|
||||
catch(Throwable e) {}
|
||||
}//finally//
|
||||
|
||||
result = new LiteList(remotePackages.getSize());
|
||||
|
||||
//Identifiy which packages have missing or stale group data.//
|
||||
for(int packageIndex = 0; packageIndex < remotePackages.getSize(); packageIndex++) {
|
||||
ResourcePackage resourcePackage = (ResourcePackage) remotePackages.get(packageIndex);
|
||||
File path = new File(application.getRemoteResourcesPath(), resourcePackage.getPath());
|
||||
|
||||
if(path.exists() && path.isFile() && path.canRead()) {
|
||||
String fileHash = FileSupport.getFileHash(path, new Sha1());
|
||||
|
||||
if(!Comparator.equals(fileHash, resourcePackage.getHash())) {
|
||||
result.add(resourcePackage);
|
||||
path.delete();
|
||||
}//if//
|
||||
}//if//
|
||||
else {
|
||||
result.add(resourcePackage);
|
||||
}//else//
|
||||
}//for//
|
||||
}//if//
|
||||
|
||||
return result;
|
||||
}//setRemoteResourcePackageData()//
|
||||
/**
|
||||
* Stores the group metadata to disk.
|
||||
* @param resourcePackage The package whose group metadata is being stored.
|
||||
* @param groupMetadata The compressed serialized group metadata for the package.
|
||||
*/
|
||||
public void storeRemoteGroup(ResourcePackage resourcePackage, byte[] groupMetadata) {
|
||||
File file = new File(application.getRemoteResourcesPath(), resourcePackage.getPath());
|
||||
|
||||
try {
|
||||
File parent = file.getParentFile();
|
||||
FileOutputStream fout;
|
||||
|
||||
if(!parent.exists()) {
|
||||
parent.mkdirs();
|
||||
}//if//
|
||||
|
||||
fout = new FileOutputStream(file, false);
|
||||
fout.write(groupMetadata);
|
||||
fout.close();
|
||||
}//try//
|
||||
catch(Throwable e) {
|
||||
Debug.log(e);
|
||||
}//catch//
|
||||
}//storeRemoteGroup()//
|
||||
/**
|
||||
* Gets the file for the given path part.
|
||||
* @param pathPart The relative path to the desired resource file.
|
||||
* @return The full path to the resource file.
|
||||
*/
|
||||
protected File getResourcePathPrefix() {
|
||||
return application.getRemoteResourcesPath();
|
||||
}//getResourcePath()//
|
||||
/**
|
||||
* Initializes the resource service.
|
||||
*/
|
||||
public void initialize(IServerSessionController sessionController) {
|
||||
try {
|
||||
this.sessionController = sessionController;
|
||||
//TODO: Load local resources when we have local resources.
|
||||
initialize(new LiteList(10, 20), remotePackages);
|
||||
}//try//
|
||||
catch(Throwable e) {
|
||||
Debug.log(e);
|
||||
}//catch//
|
||||
}//initialize()//
|
||||
/**
|
||||
* Initializes the service.
|
||||
* @param localPackages The collection of local package metadata (ResourcePackage instances).
|
||||
* @param remotePackages The collection of remote package metadata (ResourcePackage instances).
|
||||
* @throws IOException
|
||||
* @throws ClassNotFoundException
|
||||
*/
|
||||
protected void initialize(IList localPackages, IList remotePackages) throws IOException, ClassNotFoundException {
|
||||
resourcePackages = new LiteList(localPackages.getSize() + (remotePackages != null ? remotePackages.getSize() : 0));
|
||||
resourcePackages.addAll(localPackages);
|
||||
|
||||
if(remotePackages != null) {
|
||||
//TODO: Alter the paths as necessary.
|
||||
resourcePackages.addAll(remotePackages);
|
||||
}//if//
|
||||
|
||||
//Setup the package to group mapping and collect the set of all categories.//
|
||||
for(int packageIndex = 0; packageIndex < resourcePackages.getSize(); packageIndex++) {
|
||||
ResourcePackage resourcePackage = (ResourcePackage) resourcePackages.get(packageIndex);
|
||||
IList resourceGroups = loadGroups(resourcePackage);
|
||||
|
||||
packageToGroupsMap.put(resourcePackage, resourceGroups);
|
||||
|
||||
//Collect all the categories defined by this service.//
|
||||
for(int groupIndex = 0; groupIndex < resourceGroups.getSize(); groupIndex++) {
|
||||
ResourceGroup resourceGroup = (ResourceGroup) resourceGroups.get(groupIndex);
|
||||
|
||||
categories.addAll(resourceGroup.getCategories());
|
||||
}//for//
|
||||
}//for//
|
||||
|
||||
resourcePackages.isChangeable(false);
|
||||
categories.isChangeable(false);
|
||||
//TODO: Make the mapping immutable as well.
|
||||
}//initialize()//
|
||||
/* (non-Javadoc)
|
||||
* @see com.foundation.view.resource.AbstractResourceService#getResourceGroups(com.foundation.view.resource.ResourcePackage, java.lang.String)
|
||||
*/
|
||||
protected IList getResourceGroups(ResourcePackage resourcePackage, String category) {
|
||||
/* This code gets the categories and not the groups...
|
||||
IList groups = (IList) packageToGroupsMap.get(resourcePackage);
|
||||
LiteList result = new LiteList(groups.getSize());
|
||||
|
||||
//Search the groups for categories matching the passed category.//
|
||||
for(int groupIndex = 0; groupIndex < groups.getSize(); groupIndex++) {
|
||||
ResourceGroup group = (ResourceGroup) groups.get(groupIndex);
|
||||
|
||||
for(int categoryIndex = 0; categoryIndex < group.getCategories().length; categoryIndex++) {
|
||||
if(group.getCategories()[categoryIndex].getName().equals(category)) {
|
||||
result.add(group.getCategories()[categoryIndex]);
|
||||
categoryIndex = group.getCategories().length;
|
||||
}//if//
|
||||
}//for//
|
||||
}//for//
|
||||
|
||||
return result;
|
||||
*/
|
||||
return (IList) packageToGroupsMap.get(resourcePackage);
|
||||
}//getResourceGroups()//
|
||||
/* (non-Javadoc)
|
||||
* @see com.foundation.view.resource.AbstractResourceService#getResourcePackages()
|
||||
*/
|
||||
protected IList getResourcePackages() {
|
||||
return resourcePackages;
|
||||
}//getResourcePackages()//
|
||||
/* (non-Javadoc)
|
||||
* @see com.foundation.view.resource.AbstractResourceService#getResources(com.foundation.view.resource.ResourceCategory)
|
||||
*/
|
||||
protected IList getResources(ResourceCategory resourceCategory) {
|
||||
IList result = null;
|
||||
|
||||
try {
|
||||
File path = new File(getResourcePathPrefix(), resourceCategory.getPath());
|
||||
boolean downloadResources = false;
|
||||
|
||||
if(path.exists() && path.isFile() && path.canRead()) {
|
||||
String fileHash = FileSupport.getFileHash(path, new Sha1());
|
||||
|
||||
if(!Comparator.equals(fileHash, resourceCategory.getHash())) {
|
||||
path.delete();
|
||||
downloadResources = true;
|
||||
}//if//
|
||||
}//if//
|
||||
else {
|
||||
downloadResources = true;
|
||||
}//else//
|
||||
|
||||
//Download the resource file and save it to disk.//
|
||||
if(downloadResources) {
|
||||
IList compressedResourceDataBytes = sessionController.requestResourceData(new LiteList(resourceCategory));
|
||||
byte[] resourceData = (byte[]) compressedResourceDataBytes.getFirst();
|
||||
FileOutputStream fout = null;
|
||||
|
||||
//Convert the compressed serialized collection of resource package data into the object model.//
|
||||
try {
|
||||
File parent = path.getParentFile();
|
||||
|
||||
if(!parent.exists()) {
|
||||
parent.mkdirs();
|
||||
}//if//
|
||||
|
||||
fout = new FileOutputStream(path);
|
||||
fout.write(resourceData);
|
||||
}//try//
|
||||
catch(Throwable e) {
|
||||
Debug.log(e);
|
||||
}//catch//
|
||||
finally {
|
||||
try {
|
||||
fout.close();
|
||||
}//try//
|
||||
catch(Throwable e) {}
|
||||
}//finally//
|
||||
}//if//
|
||||
|
||||
result = loadResources(resourceCategory);
|
||||
}//try//
|
||||
catch(Throwable e) {
|
||||
Debug.log(e);
|
||||
}//catch//
|
||||
|
||||
return result;
|
||||
}//getResources()//
|
||||
/* (non-Javadoc)
|
||||
* @see com.foundation.view.resource.AbstractResourceService#getResourcePackageData()
|
||||
*/
|
||||
public byte[] getResourcePackageData() {
|
||||
//Not currently implemented. This is currently used only by the server side resource service. If this were implemented we would have to figure out which folder contains the data.//
|
||||
return null;
|
||||
}//getResourcePackageData()//
|
||||
/* (non-Javadoc)
|
||||
* @see com.foundation.view.resource.AbstractResourceService#getResourceGroupData(com.foundation.view.resource.ResourcePackage)
|
||||
*/
|
||||
public byte[] getResourceGroupData(ResourcePackage resourcePackage) {
|
||||
//Not currently implemented. This is currently used only by the server side resource service. If this were implemented we would have to figure out which folder contains the data.//
|
||||
return null;
|
||||
}//getResourceGroupData()//
|
||||
/* (non-Javadoc)
|
||||
* @see com.foundation.view.resource.AbstractResourceService#getResourceData(com.foundation.view.resource.ResourceCategory)
|
||||
*/
|
||||
public byte[] getResourceData(ResourceCategory resourceCategory) {
|
||||
//Not currently implemented. This is currently used only by the server side resource service. If this were implemented we would have to figure out which folder contains the data.//
|
||||
return null;
|
||||
}//getResourceData()//
|
||||
/* (non-Javadoc)
|
||||
* @see com.foundation.view.resource.AbstractResourceService#getCodeData(java.lang.String)
|
||||
*/
|
||||
public byte[] getCodeData(String name) {
|
||||
//Not currently implemented. There appears to be no need for this to be implemented in the application browser.//
|
||||
return null;
|
||||
}//getCodeData()//
|
||||
/* (non-Javadoc)
|
||||
* @see com.foundation.view.resource.AbstractResourceService#getCodeMetadata()
|
||||
*/
|
||||
public String[] getCodeMetadata() {
|
||||
//Not currently implemented. There appears to be no need for this to be implemented in the application browser.//
|
||||
return null;
|
||||
}//getCodeMetadata()//
|
||||
}//TcvResourceService//
|
||||
@@ -0,0 +1,26 @@
|
||||
/*
|
||||
* 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.client.controller;
|
||||
|
||||
import com.foundation.application.IApplication;
|
||||
import com.foundation.controller.Controller;
|
||||
|
||||
public abstract class AbstractController extends Controller {
|
||||
/**
|
||||
* AbstractController constructor.
|
||||
*/
|
||||
public AbstractController() {
|
||||
super();
|
||||
}//AbstractController()//
|
||||
/* (non-Javadoc)
|
||||
* @see com.foundation.common.IEntity#getApplication()
|
||||
*/
|
||||
public IApplication getApplication() {
|
||||
return com.foundation.tcv.client.application.AbstractClientApplication.getSingleton();
|
||||
}//getApplication()//
|
||||
}//AbstractController//
|
||||
@@ -0,0 +1,84 @@
|
||||
/*
|
||||
* 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.client.controller;
|
||||
|
||||
import com.common.util.*;
|
||||
import com.common.debug.*;
|
||||
import com.foundation.controller.DecorationManager;
|
||||
|
||||
public class ClientController extends AbstractController {
|
||||
private static ClientController singleton = new ClientController();
|
||||
private SessionController sessionController = null; //TODO: Allow multiple session controllers.//
|
||||
/**
|
||||
* ClientController constructor.
|
||||
*/
|
||||
private ClientController() {
|
||||
super();
|
||||
}//ClientController()//
|
||||
/**
|
||||
* Gets the one and only instance of this class.
|
||||
* @return The only object of this type.
|
||||
*/
|
||||
public static ClientController getSingleton() {
|
||||
return singleton;
|
||||
}//getSingleton()//
|
||||
/**
|
||||
* Gets the session controller.
|
||||
* <p>TODO: Replace this with a collection of session controllers, one for each server we connect to.</p>
|
||||
* @return The one and only session controller.
|
||||
*/
|
||||
public SessionController getSessionController() {
|
||||
return sessionController;
|
||||
}//getSessionController()//
|
||||
/**
|
||||
* Creates a connection to a server and opens the initial view.
|
||||
* @param serverAddresses The addresses (Address instances) to use while connecting to the server.
|
||||
* @param versionHandler The handler that is called if the server requires a different client version. The handler should be capable of downloading the new version.
|
||||
* @param eventSystem An object capable of processing events on the view system's event thread.
|
||||
* @return Whether the server was contacted and accepted the connection. This method will not throw exceptions.
|
||||
*/
|
||||
public boolean createSession(IList serverAddresses, IVersionHandler versionHandler, IViewSystem eventSystem) {
|
||||
try {
|
||||
sessionController = new SessionController(eventSystem);
|
||||
|
||||
if(!sessionController.initialize(this, serverAddresses, versionHandler)) {
|
||||
Debug.log("Failed to connect to the server and initialize the first view.");
|
||||
sessionController = null;
|
||||
}//if//
|
||||
}//try//
|
||||
catch(Throwable e) {
|
||||
Debug.log(e);
|
||||
}//catch//
|
||||
|
||||
return sessionController != null;
|
||||
}//connect()//
|
||||
/**
|
||||
* Forces the client to close all connections and cleanup.
|
||||
*/
|
||||
public void shutdown() {
|
||||
if(sessionController != null) {
|
||||
sessionController.shutdown();
|
||||
}//if//
|
||||
|
||||
((com.foundation.tcv.client.application.AbstractClientApplication) getApplication()).shutdown();
|
||||
}//shutdown()//
|
||||
/**
|
||||
* Destroys the session and exits the application if all sessions have been destroyed.
|
||||
* @param sessionController The removed session's controller.
|
||||
*/
|
||||
public void destroySession(SessionController sessionController) {
|
||||
this.sessionController = null;
|
||||
shutdown(); //TODO: When allowing multiple sessions we should only shutdown if all sessions are destroyed. We should also synchronize this.//
|
||||
}//destroySession()//
|
||||
/* (non-Javadoc)
|
||||
* @see com.foundation.controller.IController#getDecorationManager()
|
||||
*/
|
||||
public DecorationManager getDecorationManager() {
|
||||
return null;
|
||||
}//getDecorationManager()//
|
||||
}//ClientController//
|
||||
@@ -0,0 +1,34 @@
|
||||
/*
|
||||
* 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.client.controller;
|
||||
|
||||
import com.common.util.*;
|
||||
|
||||
public interface IVersionHandler {
|
||||
/**
|
||||
* Changes the client version by downloading the new version if necessary, and executing the new version.
|
||||
* @param newApplicationBrowserVersion The new application browser version to be downloaded, or null if everything is up to date.
|
||||
* @param newApplicationVersion The new application version to be downloaded, or null if everything is up to date.
|
||||
* @return Whether the download and/or execute succeeded.
|
||||
*/
|
||||
public boolean changeClientVersion(String newApplicationBrowserVersion, String newApplicationVersion);
|
||||
/**
|
||||
* Requests that the client download the new application version(s) in preparation for a future update.
|
||||
* If neither parameter is non-null then no action is required.
|
||||
* This method should return immediatly.
|
||||
* @param newApplicationBrowserVersion The new application browser version to be downloaded, or null if everything is up to date.
|
||||
* @param newApplicationVersion The new application version to be downloaded, or null if everything is up to date.
|
||||
*/
|
||||
public void prepareClientVersion(String newApplicationBrowserVersion, String newApplicationVersion);
|
||||
/**
|
||||
* Updates the client's list of servers for this application. Either parameter can be null indicating there is no update.
|
||||
* @param applicationServers The collection of application server addresses as strings (convertable to Address instances).
|
||||
* @param downloadServers The collection of download server addresses as strings (convertable to Address instances).
|
||||
*/
|
||||
public void updateServers(IList applicationServers, IList downloadServers);
|
||||
}//IVersionHandler//
|
||||
@@ -0,0 +1,48 @@
|
||||
/*
|
||||
* 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.client.controller;
|
||||
|
||||
import com.foundation.tcv.client.view.IAbstractClientViewComponent;
|
||||
import com.foundation.view.ViewSystemMetadata;
|
||||
|
||||
/**
|
||||
* The client controller must be provided an event system that is capable of firing events such that they are run on the view system's event thread.
|
||||
* <p>Note: This assumes all view systems are single threaded with a single event thread.</p>
|
||||
*/
|
||||
public interface IViewSystem {
|
||||
/**
|
||||
* Fires the event such that it gets run on the event thread and if synchronous only returns when complete.
|
||||
* @param event The event being fired.
|
||||
* @param isSynchronous Whether the call only returns when the event has finished running on the event thread.
|
||||
*/
|
||||
public void fireEvent(Runnable event, boolean isSynchronous);
|
||||
/**
|
||||
* Gets the client metadata which describes the client including the displays.
|
||||
* @return The metadata describing the client device.
|
||||
*/
|
||||
public ViewSystemMetadata getViewSystemMetadata();
|
||||
/**
|
||||
* Requests that the view system defer system updates (such as layouts and repaints) until the perform system updates method is called.
|
||||
* <p>Note: This method can be called multiple times with the same or different components. The method should only defer the system updates once, or should un-defer them as many times as they are deferred.</p>
|
||||
* <p>The purpose of this method is to allow the message processor to avoid repainting while initializing or applying changes to the view.</p>
|
||||
* @param component The component (which can be cast to a system specific component) that will be receiving messages.
|
||||
*/
|
||||
public void deferSystemUpdates(IAbstractClientViewComponent component);
|
||||
/**
|
||||
* Stops defering system updates and performs any waiting updates at this time.
|
||||
*/
|
||||
public void performSystemUpdates();
|
||||
/**
|
||||
* Creates a view system specific implementation of the view controller for a session.
|
||||
* @param sessionController The session controller.
|
||||
* @param number The view's unique number within the session (defined by the server).
|
||||
* @param eventSystem The handler used by the session view to fire events via the view's event system's event thread.
|
||||
* @return The instance of the view system's implementation of a view controller.
|
||||
*/
|
||||
public SessionViewController createSessionViewController(SessionController sessionController, long number, IViewSystem viewSystem);
|
||||
}//IViewSystem//
|
||||
@@ -0,0 +1,732 @@
|
||||
/*
|
||||
* Copyright (c) 2003,2008 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.client.controller;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileOutputStream;
|
||||
import java.net.URL;
|
||||
import java.net.URLClassLoader;
|
||||
|
||||
import com.common.io.*;
|
||||
import com.common.orb.*;
|
||||
import com.common.comparison.Comparator;
|
||||
import com.common.debug.*;
|
||||
import com.common.util.*;
|
||||
import com.common.util.optimized.*;
|
||||
import com.foundation.controller.DecorationManager;
|
||||
import com.foundation.tcv.controller.*;
|
||||
import com.common.security.*;
|
||||
import com.de22.orb.Address;
|
||||
import com.de22.orb.optional.SocketOptions;
|
||||
import com.foundation.tcv.client.application.AbstractClientApplication;
|
||||
import com.foundation.tcv.client.application.TcvResourceService;
|
||||
import com.foundation.tcv.view.ViewMessage;
|
||||
import com.foundation.view.ViewSystemMetadata;
|
||||
import com.foundation.view.resource.ResourcePackage;
|
||||
|
||||
/*
|
||||
* Implements a client side session controller which manages the views related to one server connection.
|
||||
*/
|
||||
public class SessionController extends AbstractController implements IClientSessionController {
|
||||
public static final boolean DEBUG = false;
|
||||
|
||||
/** The view system is used to fire events via the view system's event thread and access system metadata. */
|
||||
private IViewSystem viewSystem = null;
|
||||
private IServerSessionController serverSessionController = null;
|
||||
private LongObjectHashMap sessionViewControllerMap = new LongObjectHashMap(20);
|
||||
private ClientController clientController = null;
|
||||
private long sessionNumber = 0L;
|
||||
private String socketName = null;
|
||||
/** A collection of address formatted strings that can be used to make a server connection. */
|
||||
private IList serverAddresses = null;
|
||||
/** The currently connected address. */
|
||||
private Address currentSocketAddress = null;
|
||||
private long nextMessageOrderNumber = 0L;
|
||||
private long nextRemoteMessageOrderNumber = 0L;
|
||||
private ISignatureAlgorithm applicationCacheAlgorithm = null;
|
||||
/** The class loader to load code from cache with. */
|
||||
private URLClassLoader cacheLoader = null;
|
||||
|
||||
/**
|
||||
* Maintains the server connection data to allow error detection and reconnect capabilities.
|
||||
*/
|
||||
private static final class ConnectionData {
|
||||
private static final int STATUS_OK = 0;
|
||||
private static final int STATUS_FAILED = 1;
|
||||
private static final int STATUS_NEW_CLIENT_VERSION = 2;
|
||||
private static final int STATUS_NEW_CLIENT_VERSION_NOT_DOWNLOADED = 3;
|
||||
private static final int STATUS_APPLICATION_NOT_SERVED = 4; //Set when the application is not served by the servers that were connected to.//
|
||||
|
||||
private int status = STATUS_OK;
|
||||
private IList addresses = null;
|
||||
private IVersionHandler versionHandler = null;
|
||||
private String applicationName = null;
|
||||
private long oldSessionNumber = -1;
|
||||
private IServerSessionController connectedServerSessionController = null;
|
||||
private long connectedSessionNumber = -1;
|
||||
private Address connectedAddress = null;
|
||||
private byte[] connectedLicense = null;
|
||||
/** The server's resource package data comprised of compressed and serialized ResourcePackage instances. */
|
||||
private byte[] resourcePackageData = null;
|
||||
/** The server's jar names and jar hashes (pairs) that are required on the client. */
|
||||
private String[] codeData = null;
|
||||
|
||||
/**
|
||||
* ConnectionData constructor.
|
||||
* @param addresses
|
||||
* @param versionHandler
|
||||
* @param applicationName
|
||||
*/
|
||||
public ConnectionData(IList addresses, IVersionHandler versionHandler, String applicationName) {
|
||||
this.addresses = addresses;
|
||||
this.versionHandler = versionHandler;
|
||||
this.applicationName = applicationName;
|
||||
}//ConnectionData()//
|
||||
/**
|
||||
* ConnectionData constructor.
|
||||
* @param addresses
|
||||
* @param versionHandler
|
||||
* @param applicationName
|
||||
* @param oldSessionNumber
|
||||
*/
|
||||
public ConnectionData(IList addresses, IVersionHandler versionHandler, String applicationName, long oldSessionNumber) {
|
||||
this.addresses = addresses;
|
||||
this.versionHandler = versionHandler;
|
||||
this.applicationName = applicationName;
|
||||
this.oldSessionNumber = oldSessionNumber;
|
||||
}//ConnectionData()//
|
||||
}//ConnectionData//
|
||||
/**
|
||||
* SessionController constructor.
|
||||
* @param viewSystem The handler used by the session to fire events via the view's event system's event thread and access client metadata.
|
||||
*/
|
||||
public SessionController(IViewSystem viewSystem) {
|
||||
super();
|
||||
|
||||
this.viewSystem = viewSystem;
|
||||
}//SessionController()//
|
||||
/**
|
||||
* Initializes the session with the client controller and address list.
|
||||
* @param clientController The controller for the entire client.
|
||||
* @param applicationAddresses The collection of addresses (Address instances) to be used in making a server connection.
|
||||
* @return Whether the application is not being serviced by the addressed servers.
|
||||
*/
|
||||
public boolean initialize(ClientController clientController, IList applicationAddresses, IVersionHandler versionHandler) {
|
||||
LiteList addresses = new LiteList(applicationAddresses);
|
||||
IIterator iterator = null;
|
||||
ConnectionData connectionData = new ConnectionData(addresses, versionHandler, ((AbstractClientApplication) getApplication()).getApplicationName());
|
||||
boolean displayApplicationNotServicedMessage = false;
|
||||
|
||||
//Shuffle the addresses so that there is some random (ie even) distribution of client connections.//
|
||||
addresses.shuffle();
|
||||
iterator = addresses.iterator();
|
||||
this.clientController = clientController;
|
||||
this.serverAddresses = addresses;
|
||||
|
||||
//Iterate over the addresses to find one that accepts our connection.//
|
||||
while((currentSocketAddress == null) && (iterator != null) && (iterator.hasNext())) {
|
||||
Address address = (Address) iterator.next();
|
||||
|
||||
if(address != null) {
|
||||
Debug.log("Attempting connection on " + address);
|
||||
connect(connectionData, address);
|
||||
|
||||
//Try to connect to the server and validate the client version and the application license.//
|
||||
switch(connectionData.status) {
|
||||
case ConnectionData.STATUS_OK: {
|
||||
currentSocketAddress = connectionData.connectedAddress;
|
||||
serverSessionController = connectionData.connectedServerSessionController;
|
||||
sessionNumber = connectionData.connectedSessionNumber;
|
||||
Debug.log("Connection succeeded");
|
||||
break;
|
||||
}//case//
|
||||
case ConnectionData.STATUS_NEW_CLIENT_VERSION: {
|
||||
//Return from the method. The connect method will have already tried to startup a client of the correct version.//
|
||||
iterator = null;
|
||||
Debug.log("Connection Failed: New Client Version");
|
||||
break;
|
||||
}//case//
|
||||
case ConnectionData.STATUS_NEW_CLIENT_VERSION_NOT_DOWNLOADED: {
|
||||
//TODO: Notify the client that the application cannot start due to a problem downloading the correct client version, or due to canceling the downloading.
|
||||
iterator = null;
|
||||
Debug.log("Connection Failed: New Client Version - Not Downloaded");
|
||||
break;
|
||||
}//case//
|
||||
case ConnectionData.STATUS_FAILED: {
|
||||
//Do nothing so that we try the next address.//
|
||||
Debug.log("Connection Failed: Try Next Address");
|
||||
break;
|
||||
}//case//
|
||||
case ConnectionData.STATUS_APPLICATION_NOT_SERVED: {
|
||||
displayApplicationNotServicedMessage = true;
|
||||
Debug.log("Connection Failed: Application Not Served");
|
||||
break;
|
||||
}//case//
|
||||
default: {
|
||||
//Do nothing so that we try the next address.//
|
||||
Debug.log("Connection Status Unknown");
|
||||
}//default//
|
||||
}//switch//
|
||||
}//if//
|
||||
}//if//
|
||||
|
||||
if(currentSocketAddress != null) {
|
||||
TcvResourceService resourceService;
|
||||
IList missingResourceGroups;
|
||||
IList missingArchives;
|
||||
File applicationCacheDirectory = ((AbstractClientApplication) getApplication()).getApplicationCacheDirectory();
|
||||
//String classPath = System.getProperty("java.class.path"); //TODO: Do we want to just overwrite the classpath?
|
||||
|
||||
//Make the directories if they don't exist.//
|
||||
if(!applicationCacheDirectory.exists()) {
|
||||
applicationCacheDirectory.mkdirs();
|
||||
}//if//
|
||||
|
||||
Debug.log("Connection Succeeded");
|
||||
Orb.registerInvalidProxyListener(serverSessionController, new IInvalidProxyListener() {
|
||||
public void evaluate(Object proxy) {
|
||||
connectionLost();
|
||||
}//evaluate()//
|
||||
});
|
||||
|
||||
//Setting up the resource service.//
|
||||
Debug.log("Initializing Resources");
|
||||
resourceService = (TcvResourceService) ((AbstractClientApplication) getApplication()).getResourceService();
|
||||
missingResourceGroups = resourceService.setRemoteResourcePackageData(connectionData.resourcePackageData);
|
||||
//missingGroups = resourceService.identifyMissingRemoteGroups(new LiteList(connectionData.resourcePackages));
|
||||
|
||||
//If we are missing resource group metadata then ask the server for it now.//
|
||||
if((missingResourceGroups != null) && (missingResourceGroups.getSize() > 0)) {
|
||||
IList groupData = serverSessionController.requestResourceGroupData(missingResourceGroups);
|
||||
|
||||
for(int index = 0; index < groupData.getSize(); index++) {
|
||||
byte[] data = (byte[]) groupData.get(index);
|
||||
ResourcePackage resourcePackage = (ResourcePackage) missingResourceGroups.get(index);
|
||||
|
||||
//Store the group data to disk.//
|
||||
resourceService.storeRemoteGroup(resourcePackage, data);
|
||||
}//for//
|
||||
}//if//
|
||||
|
||||
//Initialize the resource service.//
|
||||
resourceService.initialize(serverSessionController);
|
||||
//Set the default category as the only current category for now.//
|
||||
resourceService.setCurrentCategories(System.getProperty("os.name").startsWith("Mac") ? new String[] {"Mac"} : System.getProperty("os.name").startsWith("Windows") ? new String[] {"Windows"} : new String[] {});
|
||||
|
||||
//TODO: Get the last used categories list and modify any that no longer apply (categories are slash delimited and should searched backwards).
|
||||
//TODO: Apply the categories to the metadata service - identify missing resources and request them.
|
||||
//TODO: Notify the server of the current categories?
|
||||
|
||||
if(connectionData.codeData != null) {
|
||||
URL[] urls = new URL[connectionData.codeData.length / 2];
|
||||
|
||||
missingArchives = new LiteList(connectionData.codeData.length / 2);
|
||||
|
||||
//Identify the missing code archives and request those missing or stale.//
|
||||
for(int codeIndex = 0; codeIndex < connectionData.codeData.length;) {
|
||||
String archiveName = connectionData.codeData[codeIndex++];
|
||||
String archiveHash = connectionData.codeData[codeIndex++];
|
||||
File archiveFile = new File(applicationCacheDirectory, archiveName);
|
||||
|
||||
if(archiveFile.exists()) {
|
||||
String currentHash = FileSupport.getFileHash(archiveFile, new Sha1());
|
||||
|
||||
if(!Comparator.equals(currentHash, archiveHash)) {
|
||||
archiveFile.delete();
|
||||
missingArchives.add(archiveName);
|
||||
}//if//
|
||||
}//if//
|
||||
else {
|
||||
missingArchives.add(archiveName);
|
||||
}//else//
|
||||
}//for//
|
||||
|
||||
//If there were missing archives then request their data.//
|
||||
if(missingArchives.getSize() > 0) {
|
||||
String[] archiveNames = new String[missingArchives.getSize()];
|
||||
byte[][] archiveData;
|
||||
|
||||
missingArchives.toArray(archiveNames);
|
||||
archiveData = serverSessionController.requestCodeData(archiveNames);
|
||||
|
||||
for(int archiveIndex = 0; archiveIndex < archiveData.length; archiveIndex++) {
|
||||
if(archiveData[archiveIndex] != null) {
|
||||
File archiveFile = new File(applicationCacheDirectory, archiveNames[archiveIndex]);
|
||||
|
||||
try {
|
||||
FileOutputStream fout = new FileOutputStream(archiveFile, false);
|
||||
|
||||
fout.write(archiveData[archiveIndex]);
|
||||
fout.close();
|
||||
}//try//
|
||||
catch(Throwable e) {
|
||||
Debug.log(e);
|
||||
}//catch//
|
||||
}//if//
|
||||
}//for//
|
||||
}//if//
|
||||
|
||||
//Add the archives to the class path.//
|
||||
for(int codeIndex = 0, index = 0; codeIndex < connectionData.codeData.length; index++) {
|
||||
String archiveName = connectionData.codeData[codeIndex++];
|
||||
|
||||
//Ignore the hash.//
|
||||
codeIndex++;
|
||||
|
||||
try {
|
||||
urls[index] = new URL("file:///" + new File(applicationCacheDirectory, archiveName).getCanonicalPath());
|
||||
//classPath += ";" + new File(applicationCacheDirectory, archiveName).getCanonicalPath();
|
||||
}//try//
|
||||
catch(Throwable e) {
|
||||
Debug.log(e);
|
||||
}//catch//
|
||||
}//for//
|
||||
|
||||
cacheLoader = URLClassLoader.newInstance(urls, getClass().getClassLoader());
|
||||
}//if//
|
||||
|
||||
//Save the class path.//
|
||||
//System.setProperty("java.class.path", classPath);
|
||||
|
||||
//Request the initial view.//
|
||||
Debug.log("Requesting Initial View");
|
||||
Orb.setOneWayCall(serverSessionController);
|
||||
serverSessionController.requestInitialView();
|
||||
}//if//
|
||||
else if(displayApplicationNotServicedMessage) {
|
||||
//TODO: Display a message notifying the user that the application is no longer serviced by the specified servers.
|
||||
}//else if//
|
||||
else {
|
||||
//TODO: Display a message notifying the user that the servers could not be connected to and the application will now shutdown.
|
||||
}//else//
|
||||
|
||||
return currentSocketAddress != null;
|
||||
}//initialize()//
|
||||
/* (non-Javadoc)
|
||||
* @see com.foundation.tcv.controller.IClientSessionController#setCurrentResources(com.common.util.LiteList)
|
||||
*/
|
||||
public void setCurrentResources(String[] categoryKeys) {
|
||||
//TODO: Apply the categories to the metadata service - identify missing resources and request them.
|
||||
}//setCurrentResources()//
|
||||
/**
|
||||
* <p>TODO:Rename this method.</p>
|
||||
* Gets the number identifying this session as defined by the server when the client connected.
|
||||
* @return The server session number.
|
||||
*/
|
||||
public long getNumber() {
|
||||
return sessionNumber;
|
||||
}//getNumber()//
|
||||
/**
|
||||
* Gets the class loader used to load cached code resources for the application.
|
||||
* @return The class loader that can be used to load custom application code. This may be null if no custom application code is defined.
|
||||
*/
|
||||
public URLClassLoader getCacheLoader() {
|
||||
return cacheLoader;
|
||||
}//getCacheLoader()//
|
||||
/**
|
||||
* Gets the event system that abstracts firing events via the view event thread and access client metadata.
|
||||
* @return A wrapper around the system used to display visual data.
|
||||
*/
|
||||
public IViewSystem getViewSystem() {
|
||||
return viewSystem;
|
||||
}//getViewSystem()//
|
||||
/**
|
||||
* Gets a session view controller which is managing a single view.
|
||||
* @param number The view's unique number within this session.
|
||||
* @return The view controller managing the view.
|
||||
*/
|
||||
public SessionViewController getSessionViewController(long number) {
|
||||
return (SessionViewController) sessionViewControllerMap.get(number);
|
||||
}//getSessionViewController()//
|
||||
/* (non-Javadoc)
|
||||
* @see com.foundation.tcv.controller.IClientSessionController#createView(long)
|
||||
*/
|
||||
public void createView(long number) {
|
||||
try {
|
||||
SessionViewController sessionViewController = getViewSystem().createSessionViewController(this, number, getViewSystem());
|
||||
|
||||
sessionViewControllerMap.put(number, sessionViewController);
|
||||
}//try//
|
||||
catch(Throwable e) {
|
||||
Debug.log(e);
|
||||
}//catch//
|
||||
}//createView()//
|
||||
/* (non-Javadoc)
|
||||
* @see com.foundation.tcv.controller.IClientSessionController#destroyView(long)
|
||||
*/
|
||||
public void closeView(SessionViewController sessionViewController) {
|
||||
sessionViewControllerMap.remove(sessionViewController.getNumber());
|
||||
|
||||
if(sessionViewControllerMap.getSize() == 0) {
|
||||
clientController.destroySession(this);
|
||||
}//if//
|
||||
}//destroyView()//
|
||||
/* (non-Javadoc)
|
||||
* @see com.foundation.tcv.controller.IClientSessionController#processMessages(long, long, com.common.util.IList, boolean, boolean)
|
||||
*/
|
||||
public Object processMessages(long messageOrderNumber, long viewNumber, IList viewMessages, boolean waitForResponse, boolean twoWayMessageContext) {
|
||||
SessionViewController sessionViewController = null;
|
||||
Object result = null;
|
||||
|
||||
//Locate the view that the messages belong to and have the view process them.//
|
||||
synchronized(this) {
|
||||
sessionViewController = getSessionViewController(viewNumber);
|
||||
|
||||
if(nextRemoteMessageOrderNumber != messageOrderNumber) {
|
||||
//Wait as long as we have not shutdown and this is not the next block of messages to process.//
|
||||
while((serverSessionController != null) && (nextRemoteMessageOrderNumber != messageOrderNumber)) {
|
||||
try {
|
||||
wait(1000);
|
||||
}//try//
|
||||
catch(Throwable e) {
|
||||
Debug.handle(e);
|
||||
}//catch//
|
||||
}//while//
|
||||
}//if//
|
||||
|
||||
//Note: This should not be outside the synch block since it could lead to one message processing before another.//
|
||||
//However, having it in the synch block could cause deadlocks? If deadlocks occur then we may need another solution.//
|
||||
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//
|
||||
|
||||
sessionViewController.processMessages(viewMessages, twoWayMessageContext);
|
||||
}//if//
|
||||
|
||||
//Increment the message order number after processing the messages to ensure that messages are processed in the order sent.//
|
||||
nextRemoteMessageOrderNumber++;
|
||||
}//synchronized//
|
||||
|
||||
//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((sessionViewController != null) && waitForResponse) {
|
||||
ViewMessage lastViewMessage = (ViewMessage) viewMessages.getLast();
|
||||
long returnMessageOrderNumber;
|
||||
|
||||
lastViewMessage.waitForResult(0);
|
||||
result = lastViewMessage.getResult();
|
||||
|
||||
//Get the next message number.//
|
||||
synchronized(this) {
|
||||
returnMessageOrderNumber = nextMessageOrderNumber++;
|
||||
}//synchronized//
|
||||
|
||||
//Wrapper the result of the two-way call with the ordered result object such that the server processes it in the proper order.//
|
||||
result = new com.foundation.tcv.view.OrderedResult(returnMessageOrderNumber, result);
|
||||
}//if//
|
||||
|
||||
return result;
|
||||
}//processMessages()//
|
||||
/**
|
||||
* Forces the session to close all connections and cleanup.
|
||||
*/
|
||||
public void shutdown() {
|
||||
ClientController clientController = null;
|
||||
IServerSessionController serverSessionController = null;
|
||||
|
||||
//Make sure that the shutdown only occurs once.//
|
||||
synchronized(this) {
|
||||
if(this.clientController != null) {
|
||||
clientController = this.clientController;
|
||||
serverSessionController = this.serverSessionController;
|
||||
this.clientController = null;
|
||||
this.serverSessionController = null;
|
||||
}//if//
|
||||
}//synchronized//
|
||||
|
||||
//Notify the server and close the socket.//
|
||||
if(serverSessionController != null) {
|
||||
try {
|
||||
serverSessionController.closeSession();
|
||||
}//try//
|
||||
catch(Throwable e) {
|
||||
if(!Orb.isInvalidProxyException(e)) {
|
||||
Debug.log(e);
|
||||
}//if//
|
||||
}//catch//
|
||||
|
||||
//Close the socket.//
|
||||
try {
|
||||
Orb.closeSocket(socketName, 0);
|
||||
}//try//
|
||||
catch(Throwable e) {
|
||||
Debug.handle(e);
|
||||
}//catch//
|
||||
}//if//
|
||||
|
||||
//Notify the client controller of the session's demise.//
|
||||
if(clientController != null) {
|
||||
clientController.destroySession(this);
|
||||
}//if//
|
||||
}//shutdown()//
|
||||
/**
|
||||
* Called when the connection to the server is lost. This method gives the client an opportunity to reconnect.
|
||||
*/
|
||||
private void connectionLost() {
|
||||
IList openViews = new LiteList(10, 40);
|
||||
IIterator iterator = null;
|
||||
|
||||
synchronized(this) {
|
||||
if(clientController != null) {
|
||||
iterator = sessionViewControllerMap.valueIterator();
|
||||
|
||||
while(iterator.hasNext()) {
|
||||
openViews.add(iterator.next());
|
||||
}//while//
|
||||
}//if//
|
||||
}//synchronized//
|
||||
|
||||
iterator = openViews.iterator();
|
||||
|
||||
while(iterator.hasNext()) {
|
||||
((SessionViewController) iterator.next()).disconnectedClose();
|
||||
}//while//
|
||||
|
||||
//TODO: Should we try to reconnect?
|
||||
//Use the server list to create a connection. Send the old session number to the server to attempt a reconnect.//
|
||||
//In the future we may be able to close all views and have the server send new view data using already open view controllers on the server.//
|
||||
shutdown();
|
||||
}//connectionLost()//
|
||||
/**
|
||||
* Connects to a server and opens the initial view.
|
||||
* @param serverAddress The address of the server to connect to.
|
||||
* @return If the server expected a different client version then this will be the full exe name of the expected client, already downloaded. If the server(s) could not be contacted then this value will be NO_CONNECTION. If a connection was successfully made then this method will return null.
|
||||
* @see #NO_CONNECTION
|
||||
*/
|
||||
private void connect(ConnectionData connectionData, Address serverAddress) {
|
||||
boolean tryAgain = true;
|
||||
boolean requiresValidation = false;
|
||||
|
||||
//Initialize the status to failed.//
|
||||
connectionData.status = ConnectionData.STATUS_FAILED;
|
||||
|
||||
try {
|
||||
while(tryAgain) {
|
||||
Object socketId = null;
|
||||
IThinServerController serverController = null;
|
||||
|
||||
tryAgain = false;
|
||||
socketName = serverAddress.toString();
|
||||
|
||||
//Connect to the given address. If a connection cannot be made then the application cannot be started using this address.//
|
||||
if((socketId = Orb.openSocket(socketName, new SocketOptions(serverAddress, null, null))) != null) {
|
||||
//Lookup the server controller object.//
|
||||
serverController = (IThinServerController) Orb.lookup(IThinServerController.BOUND_NAME, socketId);
|
||||
|
||||
//If we found a server controller then try to make a connection, otherwise the application cannot be started using this address.//
|
||||
if(serverController != null) {
|
||||
String applicationBrowserVersion = ((AbstractClientApplication) getApplication()).getApplicationBrowserVersion();
|
||||
String applicationVersion = ((AbstractClientApplication) getApplication()).getApplicationVersion();
|
||||
IHashMap request = new LiteHashMap(10, 20);
|
||||
LiteHashMap response = null;
|
||||
|
||||
request.put(REGISTRATION_APPLICATION_BROWSER_VERSION, applicationBrowserVersion);
|
||||
request.put(REGISTRATION_APPLICATION_VERSION, applicationVersion);
|
||||
request.put(REGISTRATION_CLIENT_CONTROLLER, (IClientSessionController) Orb.getProxy(this, IClientSessionController.class));
|
||||
request.put(REGISTRATION_APPLICATION_NAME, connectionData.applicationName);
|
||||
//Register with the server.//
|
||||
response = serverController.registerClient(request);
|
||||
|
||||
//The response may be null if the server doesn't serve the requested application.//
|
||||
if(response != null) {
|
||||
if(response.get(SESSION_DATA_REDIRECT_ADDRESSES) != null) { //Not supported currently. Should this be?//
|
||||
//IList addresses = (IList) response.get(SESSION_DATA_REDIRECT_ADDRESSES);
|
||||
|
||||
//TODO: Start the connection process over with the supplied set of addresses.
|
||||
}//if//
|
||||
else {
|
||||
IList downloadServerAddresses = (IList) response.get(SESSION_DATA_DOWNLOAD_SERVERS);
|
||||
IList applicationServerAddresses = (IList) response.get(SESSION_DATA_APPLICATION_SERVERS);
|
||||
|
||||
if(requiresValidation) {
|
||||
//Validate that the server is authentic.//
|
||||
validateLicense((byte[]) response.get(SESSION_DATA_LICENSE), serverAddress);
|
||||
}//if//
|
||||
|
||||
//Request that the addresses be updated.//
|
||||
connectionData.versionHandler.updateServers(applicationServerAddresses, downloadServerAddresses);
|
||||
|
||||
//Check to see if a new application or application browser version is required by the server.//
|
||||
if((response.get(SESSION_DATA_REQUIRED_APPLICATION_BROWSER_VERSION) != null) || (response.get(SESSION_DATA_REQUIRED_APPLICATION_VERSION) != null)) {
|
||||
String newApplicationBrowserVersion = (String) response.get(SESSION_DATA_REQUIRED_APPLICATION_BROWSER_VERSION);
|
||||
String newApplicationVersion = (String) response.get(SESSION_DATA_REQUIRED_APPLICATION_VERSION);
|
||||
|
||||
//Close the socket to the server.//
|
||||
Orb.closeSocket((String) socketName);
|
||||
|
||||
//There is a new version that is required for this application. If serverAddresses are null then download from the connected server.//
|
||||
if(connectionData.versionHandler.changeClientVersion(newApplicationBrowserVersion, newApplicationVersion)) {
|
||||
connectionData.status = ConnectionData.STATUS_NEW_CLIENT_VERSION;
|
||||
}//if//
|
||||
else {
|
||||
//The version change failed. We should probably display a dialog notifying the user and then shutdown.. or retry?//
|
||||
connectionData.status = ConnectionData.STATUS_NEW_CLIENT_VERSION_NOT_DOWNLOADED;
|
||||
}//else//
|
||||
}//if//
|
||||
else {
|
||||
//Request that the client prepare for a future upgrade by pre-downloading the installers.//
|
||||
if((response.get(SESSION_DATA_FUTURE_APPLICATION_BROWSER_VERSION) != null) || (response.get(SESSION_DATA_FUTURE_APPLICATION_VERSION) != null)) {
|
||||
connectionData.versionHandler.prepareClientVersion((String) response.get(SESSION_DATA_FUTURE_APPLICATION_BROWSER_VERSION), (String) response.get(SESSION_DATA_FUTURE_APPLICATION_VERSION));
|
||||
}//if//
|
||||
|
||||
//Check to see if we are required to connect to another application server to create our session and get our initial view.//
|
||||
if(response.get(SESSION_DATA_FORWARD_ADDRESS) != null) {
|
||||
//Close the socket, and reopen to the given address. Then reconnect, but don't revalidate. The new server should be capable of servicing the application request.//
|
||||
serverAddress = new Address((String) response.get(SESSION_DATA_FORWARD_ADDRESS));
|
||||
tryAgain = true;
|
||||
requiresValidation = false;
|
||||
Orb.closeSocket((String) socketName);
|
||||
}//if//
|
||||
else {
|
||||
//Save the session data and prepare to open the initial view.//
|
||||
connectionData.connectedSessionNumber = ((Long) response.get(SESSION_DATA_CLIENT_NUMBER)).longValue();
|
||||
connectionData.connectedServerSessionController = ((IServerSessionController) response.get(SESSION_DATA_SERVER_SESSION_CONTROLLER));
|
||||
connectionData.connectedLicense = (byte[]) response.get(SESSION_DATA_LICENSE);
|
||||
connectionData.connectedAddress = serverAddress;
|
||||
connectionData.status = ConnectionData.STATUS_OK;
|
||||
connectionData.resourcePackageData = (byte[]) response.get(SESSION_DATA_RESOURCE_PACKAGE_DATA);
|
||||
connectionData.codeData = (String[]) response.get(SESSION_DATA_CODE_DATA);
|
||||
}//else//
|
||||
}//else//
|
||||
}//else//
|
||||
}//if//
|
||||
else {
|
||||
//The server was not serving the specified (named) application.//
|
||||
connectionData.status = ConnectionData.STATUS_APPLICATION_NOT_SERVED;
|
||||
}//else//
|
||||
}//if//
|
||||
else {
|
||||
Debug.log("Error: Unable to locate the server controller on the server!");
|
||||
Orb.closeSocket(socketName);
|
||||
connectionData.status = ConnectionData.STATUS_FAILED;
|
||||
}//else//
|
||||
}//if//
|
||||
else {
|
||||
Debug.log("Warning: Unable to open a socket to the server.");
|
||||
connectionData.status = ConnectionData.STATUS_FAILED;
|
||||
}//else//
|
||||
}//while//
|
||||
}//try//
|
||||
catch(Throwable e) {
|
||||
Debug.log(e, "Caught while attempting a connection to: " + serverAddress);
|
||||
connectionData.status = ConnectionData.STATUS_FAILED;
|
||||
}//catch//
|
||||
}//connect()//
|
||||
/**
|
||||
* Validates the license and ensures that it came from a valid address.
|
||||
* If the license does not exist then an invalid license view will appear and will shut the application down when it is closed.
|
||||
* If the license is invalid, old, or does not specify the address that it originated from, then the application will notify the user and immediatly shutdown.
|
||||
* @param licenseData The data that makes up the license.
|
||||
* @param serverAddress The address the license came from.
|
||||
*/
|
||||
private void validateLicense(byte[] licenseData, Address serverAddress) {
|
||||
boolean isValid = false;
|
||||
|
||||
if(licenseData != null) {
|
||||
try {
|
||||
ISignatureAlgorithm algorithm = new RsaAlgorithm(((AbstractClientApplication) getApplication()).getValidationData());
|
||||
ByteArrayInputStream bin = new ByteArrayInputStream(licenseData);
|
||||
SignatureInputStream sin = new SignatureInputStream(bin, algorithm);
|
||||
HashedInputStream hin = new HashedInputStream(sin, new Sha1());
|
||||
CompressionInputStream cin = new CompressionInputStream(hin);
|
||||
com.common.io.ObjectInputStream in = new com.common.io.ObjectInputStream(cin, null, null);
|
||||
LiteHashMap dataMap = new LiteHashMap(10);
|
||||
|
||||
dataMap.readExternal(in);
|
||||
in.decrypt(true);
|
||||
isValid = hin.validateHash();
|
||||
in.decrypt(false);
|
||||
}//try//
|
||||
catch(Throwable e) {
|
||||
Debug.log(e);
|
||||
}//catch//
|
||||
}//if//
|
||||
|
||||
if(licenseData == null) {
|
||||
((AbstractClientApplication) getApplication()).displayNoLicenseSplash();
|
||||
}//if//
|
||||
else if(!isValid) {
|
||||
((AbstractClientApplication) getApplication()).displayInvalidLicenseSplash();
|
||||
}//else if//
|
||||
else {
|
||||
//TODO: Should we display a splash screen?
|
||||
}//else//
|
||||
}//validateLicense()//
|
||||
/**
|
||||
* Sends a collection messages to the server.
|
||||
* @param viewNumber The number of the view sending the messages.
|
||||
* @param messages The collection of messages to be sent.
|
||||
* @param resultCallback A handler which is given the result by the orb when it becomes available. If this is null then the request is one way.
|
||||
*/
|
||||
public boolean sendMessages(long viewNumber, IList messages, IResultCallback resultCallback) {
|
||||
boolean success = true;
|
||||
long messageOrderNumber;
|
||||
IServerSessionController serverSessionController = null;
|
||||
|
||||
synchronized(this) {
|
||||
serverSessionController = this.serverSessionController;
|
||||
messageOrderNumber = nextMessageOrderNumber++;
|
||||
}//synchronized//
|
||||
|
||||
//Make sure we did not shutdown.//
|
||||
if(serverSessionController != null) {
|
||||
try {
|
||||
if(resultCallback == null) {
|
||||
Orb.setOneWayCall(serverSessionController);
|
||||
}//if//
|
||||
else {
|
||||
Orb.setResultCallback(serverSessionController, resultCallback);
|
||||
}//else//
|
||||
|
||||
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//
|
||||
|
||||
serverSessionController.processMessages(messageOrderNumber, viewNumber, messages, resultCallback != null);
|
||||
}//try//
|
||||
catch(Throwable e) {
|
||||
if(Orb.isInvalidProxyException(e)) {
|
||||
//TODO: Try to reconnect?
|
||||
//Note: Since we may have shutdown, we should verify that we have not before trying to reconnect.//
|
||||
success = false;
|
||||
}//if//
|
||||
else {
|
||||
Debug.log(e, "This exception is likely to have occured on the server. The client is unlikely to recover from this exception.");
|
||||
//TODO: What to do if there was some exception on the server?
|
||||
//Note: It is possible that another thread shut the session down between the time we got the server session controller ref and now.//
|
||||
success = false;
|
||||
}//else//
|
||||
}//catch//
|
||||
}//if//
|
||||
|
||||
return success;
|
||||
}//sendMessage()//
|
||||
/* (non-Javadoc)
|
||||
* @see com.foundation.tcv.controller.IClientSessionController#getViewSystemMetadata()
|
||||
*/
|
||||
public ViewSystemMetadata getViewSystemMetadata() {
|
||||
return viewSystem.getViewSystemMetadata();
|
||||
}//getViewSystemMetadata()//
|
||||
/* (non-Javadoc)
|
||||
* @see com.foundation.controller.IController#getDecorationManager()
|
||||
*/
|
||||
public DecorationManager getDecorationManager() {
|
||||
return null;
|
||||
}//getDecorationManager()//
|
||||
}//SessionController//
|
||||
@@ -0,0 +1,565 @@
|
||||
/*
|
||||
* 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.client.controller;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileOutputStream;
|
||||
import com.common.util.*;
|
||||
import com.common.debug.*;
|
||||
import com.common.util.optimized.*;
|
||||
import com.foundation.tcv.view.*;
|
||||
import com.foundation.tcv.client.view.*;
|
||||
import com.foundation.tcv.controller.*;
|
||||
import com.foundation.view.IRemoteViewContext.LoadStreamedData;
|
||||
|
||||
/**
|
||||
* The session view is a display on the client which exists within a server connection context (the session).
|
||||
* This controller manages the session view and funnels messages to and from the client visual components and the server components.
|
||||
*/
|
||||
public abstract class SessionViewController extends AbstractController implements ISessionViewController {
|
||||
/** The session under which this view was created. */
|
||||
private SessionController sessionController = null;
|
||||
/** The mapping of view components by thier identifying numbers. */
|
||||
private IntObjectHashMap componentMap = new IntObjectHashMap(20);
|
||||
/** The queue of unprocessed outgoing messages. */
|
||||
private Queue outgoingMessageQueue = new Queue(100);
|
||||
/** The number of holds placed on outgoing messages. */
|
||||
private int messageHoldCount = 0;
|
||||
/** The unique number assigned to this session view. */
|
||||
private long number = 0L;
|
||||
/** The number of synchronous messages sent to the server. If this is greater than zero then all incomming messages without the two-way flag set will be queued by the event thread and processed later. */
|
||||
private int synchronousMessageCount = 0;
|
||||
/** A queue of messages (pulled from the view event queue) waiting for the current two-way message to finish processing before they get placed back in the view's event queue. */
|
||||
private Queue pendingQueuedMessages = new Queue(20);
|
||||
/** A queue of messages (received after the two way message started) waiting for the current two-way message to finish processing before they get placed back in the view's event queue. */
|
||||
private Queue pendingUnqueuedMessages = new Queue(20);
|
||||
/** The handler used by the session view to fire events via the view's event system's event thread and access system metadata. */
|
||||
private IViewSystem viewSystem = null;
|
||||
|
||||
/**
|
||||
* A simple wrapper around the message to execute it on the view system's event thread.
|
||||
*/
|
||||
private class MessageRunnable implements Runnable {
|
||||
//private ViewMessage viewMessage = null;
|
||||
private IList viewMessages = null;
|
||||
private boolean twoWayContext = false;
|
||||
|
||||
/**
|
||||
* MessageRunnable constructor.
|
||||
* @param viewMessages The view messages to be processed.
|
||||
* @param twoWayContext Whether the message was sent within a two way message context created by a client originated two way message.
|
||||
*/
|
||||
private MessageRunnable(IList viewMessages, boolean twoWayContext) {
|
||||
this.viewMessages = viewMessages;
|
||||
this.twoWayContext = twoWayContext;
|
||||
}//MessageRunnable()//
|
||||
public void run() {
|
||||
try {
|
||||
boolean queue = false;
|
||||
|
||||
//Determine whether we must queue the request.//
|
||||
synchronized(SessionViewController.this) {
|
||||
queue = (synchronousMessageCount > 0) && (!twoWayContext);
|
||||
|
||||
if(queue) {
|
||||
if(DEBUG) {
|
||||
Debug.log("Queuing (Queued) Messages: " + viewMessages);
|
||||
}//if//
|
||||
|
||||
//Queue the request to be processed later when we are done with the current two-way messages.//
|
||||
//Note: We place this in the pending queued messages queue so that they are the first to be placed back into the event queue, thus preserving the ordering.//
|
||||
pendingQueuedMessages.enqueue(this);
|
||||
}//if//
|
||||
}//synchronized//
|
||||
|
||||
if(!queue) {
|
||||
if(DEBUG) {
|
||||
Debug.log("Processing Messages: " + viewMessages);
|
||||
}//if//
|
||||
|
||||
try {
|
||||
IIterator iterator = viewMessages.iterator();
|
||||
|
||||
while(iterator.hasNext()) {
|
||||
ViewMessage viewMessage = (ViewMessage) iterator.next();
|
||||
|
||||
if(viewMessage.getControlNumber() == 0) {
|
||||
//Create the component.//
|
||||
switch(viewMessage.getMessageNumber()) {
|
||||
case com.foundation.tcv.view.IAbstractRemoteViewComponent.MESSAGE_CREATE_COMPONENT: {
|
||||
try {
|
||||
int componentNumber = viewMessage.getMessageInteger();
|
||||
Class componentType = null;
|
||||
IAbstractClientViewComponent component;
|
||||
String className = (String) viewMessage.getMessageData();
|
||||
|
||||
try {
|
||||
componentType = Class.forName(className);
|
||||
}//try//
|
||||
catch(ClassNotFoundException e) {
|
||||
//Ignore it.//
|
||||
}//catch//
|
||||
|
||||
if((componentType == null) && (getSessionController().getCacheLoader() != null)) {
|
||||
componentType = getSessionController().getCacheLoader().loadClass(className);
|
||||
}//if//
|
||||
|
||||
component = (IAbstractClientViewComponent) componentType.newInstance();
|
||||
|
||||
synchronized(SessionViewController.this) {
|
||||
componentMap.put(componentNumber, component);
|
||||
}//synchronized//
|
||||
|
||||
component.initialize(componentNumber, SessionViewController.this);
|
||||
}//try//
|
||||
catch(Throwable e) {
|
||||
Debug.log(e, "Error: Unable to create the requested view component: " + viewMessage.getMessageData());
|
||||
//TODO: Place a dummy view component so the view doesn't fail? Perhaps an empty panel?//
|
||||
}//catch//
|
||||
|
||||
viewMessage.setResult(null);
|
||||
break;
|
||||
}//case//
|
||||
case com.foundation.tcv.view.IAbstractRemoteViewComponent.MESSAGE_DESTROY_COMPONENT: { //This case allows the client to destroy its own components (if the server is disconnected).//
|
||||
|
||||
try {
|
||||
Object messageData = (Object) viewMessage.getMessageData();
|
||||
IAbstractClientViewComponent component = (IAbstractClientViewComponent) messageData;
|
||||
|
||||
component.processMessage(viewMessage);
|
||||
}//try//
|
||||
catch(Throwable e) {
|
||||
Debug.log(e, "Error: Unable to create the requested view component: " + viewMessage.getMessageData());
|
||||
//TODO: Place a dummy view component so the view doesn't fail? Perhaps an empty panel?//
|
||||
}//catch//
|
||||
|
||||
viewMessage.setResult(null);
|
||||
break;
|
||||
}//case//
|
||||
case com.foundation.tcv.view.IAbstractRemoteViewComponent.MESSAGE_CLOSE_VIEW: {
|
||||
close();
|
||||
break;
|
||||
}//case//
|
||||
case MESSAGE_OPEN_TEMP_FILE: {
|
||||
if(viewMessage.getMessageData() instanceof Object[]) {
|
||||
Object[] messageData = (Object[]) viewMessage.getMessageData();
|
||||
String extension = messageData != null ? ((String) messageData[0]).trim() : null;
|
||||
String fileName = messageData != null ? ((String) messageData[1]).trim() : null;
|
||||
byte[] data = messageData != null ? (byte[]) messageData[2] : null;
|
||||
|
||||
if((extension != null) && (extension.length() > 0)/* && (!extension.equalsIgnoreCase("exe"))*/ && (data != null) && (data.length > 0)) {
|
||||
File file = File.createTempFile(fileName == null ? "data" : fileName, '.' + extension);
|
||||
FileOutputStream out = new FileOutputStream(file.getAbsolutePath(), false);
|
||||
String osName = System.getProperty("os.name");
|
||||
String command = "cmd.exe /C ";
|
||||
|
||||
out.write(data);
|
||||
out.close();
|
||||
|
||||
if(osName.equalsIgnoreCase("Windows 95")) {
|
||||
command = "command.com /C ";
|
||||
}//if//
|
||||
else if(osName.equalsIgnoreCase("Mac OS X")) {
|
||||
command = "open -n ";
|
||||
}//else if//
|
||||
|
||||
Runtime.getRuntime().exec(command + file.getAbsolutePath());
|
||||
}//if//
|
||||
}//if//
|
||||
break;
|
||||
}//case//
|
||||
case MESSAGE_SAVE_TO_FILE: {
|
||||
if(viewMessage.getMessageData() instanceof Object[]) {
|
||||
Object[] messageData = (Object[]) viewMessage.getMessageData();
|
||||
|
||||
if(messageData != null) {
|
||||
String defaultFileName = ((String) messageData[0]).trim();
|
||||
byte[] data = (byte[]) messageData[1];
|
||||
|
||||
saveToFile(defaultFileName, data);
|
||||
}//if//
|
||||
}//if//
|
||||
break;
|
||||
}//case//
|
||||
case MESSAGE_LOAD_FROM_FILE: {
|
||||
if(viewMessage.getMessageData() instanceof Object[]) {
|
||||
Object[] messageData = (Object[]) viewMessage.getMessageData();
|
||||
|
||||
if(messageData != null) {
|
||||
String[] extensions = (String[]) messageData[0];
|
||||
Boolean allowMultiple = (Boolean) messageData[1];
|
||||
|
||||
viewMessage.setResult(loadFromFile(extensions, allowMultiple.booleanValue()));
|
||||
}//if//
|
||||
}//if//
|
||||
break;
|
||||
}//case//
|
||||
case MESSAGE_SAVE_TO_FILE_STREAMED: {
|
||||
String defaultPath = (String) ((Object[]) viewMessage.getMessageData())[0];
|
||||
String defaultFileName = (String) ((Object[]) viewMessage.getMessageData())[1];
|
||||
IContentStream contentStream = (IContentStream) viewMessage.getMessageSecondaryData();
|
||||
long contentSize = ((Long) ((Object[]) viewMessage.getMessageData())[2]).longValue();
|
||||
|
||||
viewMessage.setResult(saveToFileStreamed(defaultPath, defaultFileName, contentSize, contentStream));
|
||||
break;
|
||||
}//case//
|
||||
case MESSAGE_LOAD_FROM_FILE_STREAMED: {
|
||||
String[] extensions = (String[]) viewMessage.getMessageData();
|
||||
Object[] optionalParams = (Object[]) viewMessage.getMessageSecondaryData();
|
||||
boolean allowMultiple = viewMessage.getMessageInteger() != 0;
|
||||
String fileName = optionalParams != null && optionalParams.length > 0 && optionalParams[0] instanceof String ? (String) optionalParams[0] : null;
|
||||
String filePath = optionalParams != null && optionalParams.length > 1 && optionalParams[1] instanceof String ? (String) optionalParams[1] : null;
|
||||
|
||||
viewMessage.setResult(loadFromFileStreamed(extensions, allowMultiple, fileName, filePath));
|
||||
break;
|
||||
}//case//
|
||||
case MESSAGE_SAVE_TO_FILES_STREAMED: {
|
||||
String defaultPath = (String) ((Object[]) viewMessage.getMessageData())[0];
|
||||
String[] fileNames = (String[]) ((Object[]) viewMessage.getMessageData())[1];
|
||||
IContentStream[] contentStreams = (IContentStream[]) viewMessage.getMessageSecondaryData();
|
||||
|
||||
viewMessage.setResult(new Integer(saveToFilesStreamed(defaultPath, fileNames, contentStreams)));
|
||||
break;
|
||||
}//case//
|
||||
case MESSAGE_COLLECT_FILES_TO_RENAME: {
|
||||
String[] extensions = (String[]) viewMessage.getMessageData();
|
||||
Object[] optionalParams = (Object[]) viewMessage.getMessageSecondaryData();
|
||||
boolean allowMultiple = viewMessage.getMessageInteger() != 0;
|
||||
String fileName = optionalParams != null && optionalParams.length > 0 && optionalParams[0] instanceof String ? (String) optionalParams[0] : null;
|
||||
String filePath = optionalParams != null && optionalParams.length > 1 && optionalParams[1] instanceof String ? (String) optionalParams[1] : null;
|
||||
|
||||
viewMessage.setResult(collectFilesToRename(extensions, allowMultiple, fileName, filePath));
|
||||
break;
|
||||
}//case//
|
||||
case MESSAGE_RENAME_FILES: {
|
||||
String[] newFileNames = (String[]) viewMessage.getMessageData();
|
||||
boolean includesPaths = viewMessage.getMessageInteger() != 0;
|
||||
|
||||
renameFiles(newFileNames, includesPaths);
|
||||
viewMessage.setResult(null);
|
||||
break;
|
||||
}//case//
|
||||
default: {
|
||||
//Unexpected message number.//
|
||||
Debug.log("Error: Unexpected SessionViewController message number: " + viewMessage.getMessageNumber());
|
||||
viewMessage.setResult(null);
|
||||
}//default//
|
||||
}//else//
|
||||
}//if//
|
||||
else {
|
||||
//Pass the message to the target component.//
|
||||
IAbstractClientViewComponent component = (IAbstractClientViewComponent) getComponent(viewMessage.getControlNumber());
|
||||
|
||||
//The component might be null if this message was delayed due to a round trip message such as the user closing the view via a button press. In that situation the control might have been released prior to this message being processed in which case the message is irrelevant.//
|
||||
if(component != null) {
|
||||
viewSystem.deferSystemUpdates(component);
|
||||
viewMessage.setResult(component.processMessage(viewMessage));
|
||||
}//if//
|
||||
else {
|
||||
viewMessage.setResult(null);
|
||||
}//else//
|
||||
}//else//
|
||||
}//while//
|
||||
}//try//
|
||||
finally {
|
||||
viewSystem.performSystemUpdates();
|
||||
}//finally//
|
||||
}//if//
|
||||
}//try//
|
||||
catch(Throwable e) {
|
||||
Debug.log(e);
|
||||
}//catch//
|
||||
}//run()//
|
||||
}//MessageRunnable//
|
||||
/**
|
||||
* SessionViewController constructor.
|
||||
* @param sessionController The session controller.
|
||||
* @param number The view's unique number within the session (defined by the server).
|
||||
* @param eventSystem The handler used by the session view to fire events via the view's event system's event thread.
|
||||
*/
|
||||
public SessionViewController(SessionController sessionController, long number, IViewSystem viewSystem) {
|
||||
super();
|
||||
|
||||
this.sessionController = sessionController;
|
||||
this.number = number;
|
||||
this.viewSystem = viewSystem;
|
||||
}//SessionViewController()//
|
||||
/**
|
||||
* Gets the view's unique number within the session (defined by the server).
|
||||
* @return The server defined view number.
|
||||
*/
|
||||
public long getNumber() {
|
||||
return number;
|
||||
}//getNumber()//
|
||||
/**
|
||||
* Gets the session controller.
|
||||
* @return The session that this view controller exists under.
|
||||
*/
|
||||
public SessionController getSessionController() {
|
||||
return sessionController;
|
||||
}//getSessionController()//
|
||||
/**
|
||||
* Gets the component map for internal use only. The caller should synchronize when using the component map since other threads could atler it.
|
||||
* @return The mapping of view components by their numbers.
|
||||
*/
|
||||
protected IntObjectHashMap getComponentMap() {
|
||||
return componentMap;
|
||||
}//getComponentMap()//
|
||||
/**
|
||||
* Gets the view component with the given number.
|
||||
* <p>TODO: Analyze whether this method should or should not be synchronous.</p>
|
||||
* @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()//
|
||||
/**
|
||||
* Sends a message to the server.
|
||||
* @param viewMessage The message to be sent.
|
||||
* @param resultHandler The optional handler that will be called when the result gets back. If this is null then the message does not require a result and is one-way.
|
||||
* @return Whether the message was successfully sent.
|
||||
*/
|
||||
public synchronized boolean sendMessage(ViewMessage viewMessage, IResultCallback resultHandler) {
|
||||
boolean result = false;
|
||||
|
||||
//The session controller will be null if the view has been disbanded. It is possible that after releasing a view that a synchronize or refresh task may try to run.//
|
||||
if(getSessionController() != null) {
|
||||
outgoingMessageQueue.enqueue(viewMessage);
|
||||
|
||||
//Send the message now if we are not queuing messages or the message is not asynchronous.//
|
||||
if((resultHandler != null) || (messageHoldCount == 0)) {
|
||||
result = sendMessages(resultHandler);
|
||||
}//if//
|
||||
}//if//
|
||||
|
||||
return result;
|
||||
}//sendMessage()//
|
||||
/**
|
||||
* Sends queued messages to the server and may wait for a response.
|
||||
* <p>Note: The callers of this method must hold a synch lock on this object.</p>
|
||||
* @param resultHandler A handler that will process the result when it arrives. If this is null, then the queued messages don't require any results and are one way.
|
||||
* @return Whether the message was successfully sent.
|
||||
*/
|
||||
private boolean sendMessages(final IResultCallback resultHandler) {
|
||||
boolean result = false;
|
||||
IResultCallback callback = resultHandler;
|
||||
|
||||
if(outgoingMessageQueue.getSize() > 0) {
|
||||
IList messages = new LiteList(outgoingMessageQueue.getSize());
|
||||
|
||||
//If this is a synchronous message (requires a result) then increment the count and setup a handler to decrement the count and process queued messages.//
|
||||
if(resultHandler != null) {
|
||||
final IViewSystem viewSystem = this.viewSystem;
|
||||
|
||||
synchronousMessageCount++;
|
||||
|
||||
//Wrapper the callback with another callback which will properly process all incomming messages that got queued.//
|
||||
callback = new IResultCallback() {
|
||||
/* (non-Javadoc)
|
||||
* @see com.common.orb.IResultCallback#run(java.lang.Object)
|
||||
*/
|
||||
public void run(final Object result) {
|
||||
//Fire an event via the view event system so that the result is processed on the event thread.//
|
||||
viewSystem.fireEvent(new Runnable() {
|
||||
public void run() {
|
||||
//Process the result.//
|
||||
resultHandler.run(result);
|
||||
|
||||
//Update the synchronous message count and process any pending messages.//
|
||||
synchronized(this) {
|
||||
synchronousMessageCount--;
|
||||
|
||||
//Process any messages that came in while handling the 2-way message that were unrelated.//
|
||||
if(synchronousMessageCount == 0) {
|
||||
//Place the pending messages back in the view system event queue.//
|
||||
processPendingMessages();
|
||||
}//if//
|
||||
}//synchronized//
|
||||
}
|
||||
}, false);
|
||||
}//run()//
|
||||
};
|
||||
}//if//
|
||||
|
||||
//Create a list of the queued messages.//
|
||||
while(outgoingMessageQueue.getSize() > 0) {
|
||||
messages.add(outgoingMessageQueue.dequeue());
|
||||
}//while//
|
||||
|
||||
//Send the queued messages to the client.//
|
||||
result = getSessionController().sendMessages(number, messages, callback);
|
||||
}//if//
|
||||
|
||||
return result;
|
||||
}//sendMessage()//
|
||||
/**
|
||||
* Process any messages that came in while handling the 2-way message that were unrelated.
|
||||
* This method just places the messages back into the event queue to be processed on the event thread.
|
||||
*/
|
||||
private void processPendingMessages() {
|
||||
//To ensure proper order, process messages already queued at the time the two way message was sent, then those received after it was sent.//
|
||||
while(pendingQueuedMessages.getSize() > 0) {
|
||||
MessageRunnable next = (MessageRunnable) pendingQueuedMessages.dequeue();
|
||||
|
||||
viewSystem.fireEvent(next, false);
|
||||
}//while//
|
||||
|
||||
//Queue up those messages received after the two way message was sent by this client view.//
|
||||
while(pendingUnqueuedMessages.getSize() > 0) {
|
||||
MessageRunnable next = (MessageRunnable) pendingUnqueuedMessages.dequeue();
|
||||
|
||||
viewSystem.fireEvent(next, false);
|
||||
}//while//
|
||||
}//processPendingMessages()//
|
||||
/**
|
||||
* 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++;
|
||||
}//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--;
|
||||
|
||||
if((messageHoldCount == 0) && (outgoingMessageQueue.getSize() > 0)) {
|
||||
sendMessages(null);
|
||||
}//if//
|
||||
}//decrementMessageHoldCount()//
|
||||
/**
|
||||
* Closes and destroys the view.
|
||||
*/
|
||||
public void close() {
|
||||
SessionController sessionController = null;
|
||||
|
||||
synchronized(this) {
|
||||
sessionController = this.sessionController;
|
||||
this.sessionController = null;
|
||||
}//synchronized//
|
||||
|
||||
if(sessionController != null) {
|
||||
sessionController.closeView(this);
|
||||
//TODO: Verify that all components are unregsitered.//
|
||||
}//if//
|
||||
}//close()//
|
||||
/**
|
||||
* Allows the view to be closed when the server connection is lost.
|
||||
* <p>TODO: If we are going to allow customized saved data for restoration upon reconnecting we should do so here.
|
||||
* Each component should probably be given an opportunity to synchronize its contents to a structure that can be serialized.
|
||||
* The structure should be reconsituted and given to the view components after they are re-initialized after reconnecting.</p>
|
||||
*/
|
||||
protected void disconnectedClose() {
|
||||
SessionController sessionController = null;
|
||||
IAbstractClientViewComponent component = null;
|
||||
|
||||
synchronized(this) {
|
||||
component = (IAbstractClientViewComponent) componentMap.get(1);
|
||||
|
||||
sessionController = this.sessionController;
|
||||
this.sessionController = null;
|
||||
}//synchronized//
|
||||
|
||||
if(sessionController != null) {
|
||||
if(component != null) {
|
||||
//Send a fake message supposidly from the server to request that the components all be destroyed.//
|
||||
processMessages(new LiteList(new ViewMessage(0, com.foundation.tcv.view.IAbstractRemoteViewComponent.MESSAGE_DESTROY_COMPONENT, component, null, 0, 0, false)), true);
|
||||
}//if//
|
||||
|
||||
sessionController.closeView(this);
|
||||
//TODO: Verify that all components are unregsitered.//
|
||||
}//if//
|
||||
}//disconnectedClose()//
|
||||
/**
|
||||
* Releases the component from the view.
|
||||
* @param componentNumber The number of the component to be released.
|
||||
*/
|
||||
public synchronized void releaseComponent(int componentNumber) {
|
||||
componentMap.remove(componentNumber);
|
||||
}//releaseComponent()//
|
||||
/**
|
||||
* Processes the messages.
|
||||
* @param viewMessages The collection of messages to be processed in order.
|
||||
* @param twoWayMessageContext Whether the server is sending the messages in response to a two way message sent by this client. If true then these messages have priority and must be processed immediatly.
|
||||
*/
|
||||
public void processMessages(final IList viewMessages, boolean twoWayMessageContext) {
|
||||
if(twoWayMessageContext || (synchronousMessageCount == 0)) {
|
||||
MessageRunnable runnable = new MessageRunnable(viewMessages, twoWayMessageContext);
|
||||
|
||||
//Send the message to the view's event queue to be processed on the event thread.//
|
||||
viewSystem.fireEvent(runnable, false); //Note: We will never wait here for the message to be processed since this will cause deadlocks. Instead the caller must wait (the message has facilities for this) outside of the sync blocks.//
|
||||
}//if//
|
||||
else {
|
||||
synchronized(this) {
|
||||
MessageRunnable runnable = new MessageRunnable(viewMessages, twoWayMessageContext);
|
||||
|
||||
if(DEBUG) {
|
||||
Debug.log("Queuing (Unqueued) Messages: " + viewMessages);
|
||||
}//if//
|
||||
|
||||
//Queue the runnable up to be placed in the view's event queue when the current two way message completes.//
|
||||
//Note: We place the message in the unqueued messages queue so that they get placed in the event queue after the ones pulled from the event queue, thus preserving the order.//
|
||||
pendingUnqueuedMessages.enqueue(runnable);
|
||||
}//synchronized//
|
||||
}//else//
|
||||
}//processMessages()//
|
||||
/**
|
||||
* Saves the data to a file of the user's choosing.
|
||||
* @param defaultFileName The optional default file name.
|
||||
* @param data The data to be saved.
|
||||
*/
|
||||
protected abstract void saveToFile(String defaultFileName, byte[] data);
|
||||
/**
|
||||
* Loads data from a file of the user's choosing.
|
||||
* @param extensions The optional file extension which tells the system what file types to allow. The extensions should be formatted similar to: '*.pdf' or '*.*'.
|
||||
* @return The data from the file chosen by the user (an array of selection arrays, each inner selection array contains a string which is the file name and a byte array - the contents), or null if the user did not choose a file.
|
||||
*/
|
||||
protected abstract Object[][] loadFromFile(String[] extensions, boolean allowMultiple);
|
||||
/**
|
||||
* Saves the data to a file of the user's choosing.
|
||||
* @param defaultPath The optional default path.
|
||||
* @param defaultFileName The optional default file name.
|
||||
* @param contentSize The optional size of the content. A value less than zero indicates that the size is unkown.
|
||||
* @param contentStream The content to be streamed to the client as the client is able to handle it.
|
||||
* @return The path and file name that the content was saved to.
|
||||
*/
|
||||
protected abstract String saveToFileStreamed(String defaultPath, String defaultFileName, long contentSize, IContentStream contentStream);
|
||||
/**
|
||||
* Loads data from a file of the user's choosing.
|
||||
* @param extensions The optional file extension which tells the system what file types to allow. The extensions should be formatted similar to: '*.pdf' or '*.*'.
|
||||
* @param allowMultiple Whether multiple files can be selected.
|
||||
* @param fileName The optional file name. This can include the path.
|
||||
* @param filterPath The optional filter path. This is not necessary if fileName contains a path.
|
||||
* @return The selected files wrappered in LoadStreamedData structures, or null if the user cancelled or otherwise selected nothing.
|
||||
*/
|
||||
protected abstract LoadStreamedData[] loadFromFileStreamed(String[] extensions, boolean allowMultiple, String fileName, String filterPath);
|
||||
/**
|
||||
* Saves a bunch of files to the same directory.
|
||||
* @param defaultPath The default directory to save to.
|
||||
* @param fileNames The names of the files (must be the same length as content streams).
|
||||
* @param contentStreams The content streams for each file.
|
||||
* @return The count of streams written to files.
|
||||
*/
|
||||
protected abstract int saveToFilesStreamed(String defaultPath, String[] fileNames, IContentStream[] contentStreams);
|
||||
/**
|
||||
* Collects files that may need renaming.
|
||||
* @param extensions The optional file extension which tells the system what file types to allow. The extensions should be formatted similar to: '*.pdf' or '*.*'.
|
||||
* @param allowMultiple Whether multiple files can be selected.
|
||||
* @param fileName The optional file name. This can include the path.
|
||||
* @param filterPath The optional filter path. This is not necessary if fileName contains a path.
|
||||
* @return The array of paths and file names selected by the user.
|
||||
*/
|
||||
protected abstract String[] collectFilesToRename(String[] extensions, boolean allowMultiple, String fileName, String filterPath);
|
||||
/**
|
||||
* Renames the previously collected files.
|
||||
* @param newFileNames The array of new file names (and optional paths). This must be equal in length to the array returned by the last collect call.
|
||||
* @param includesPaths Whether the file names will include paths (and move the files).
|
||||
*/
|
||||
protected abstract void renameFiles(String[] newFileNames, boolean includesPaths);
|
||||
}//SessionViewController//
|
||||
@@ -0,0 +1,26 @@
|
||||
/*
|
||||
* 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.client.view;
|
||||
|
||||
import com.foundation.tcv.view.*;
|
||||
import com.foundation.tcv.client.controller.*;
|
||||
import com.foundation.view.IResourceHolderComponent;
|
||||
|
||||
public interface IAbstractClientViewComponent extends IAbstractRemoteViewComponent, IResourceHolderComponent {
|
||||
/**
|
||||
* Initializes the component.
|
||||
* @param number The component's number.
|
||||
* @param controller The component's view controller.
|
||||
*/
|
||||
public void initialize(int number, SessionViewController sessionViewController);
|
||||
/**
|
||||
* Gets the component's view controller.
|
||||
* @return The view component's view controller.
|
||||
*/
|
||||
public SessionViewController getSessionViewController();
|
||||
}//IAbstractClientViewComponent//
|
||||
@@ -0,0 +1,33 @@
|
||||
/*
|
||||
* Copyright (c) 2006,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.client.view;
|
||||
|
||||
import com.foundation.view.IResourceHolderComponent;
|
||||
import com.foundation.view.resource.AbstractResourceService;
|
||||
|
||||
/**
|
||||
* Used by components that may receive either a static value or a reference to a resource.
|
||||
* This resource association will take the value, register listeners if it is a resource, get the actual value if it is a resource, and notify the component when the value is set or is changed.
|
||||
* <p><b>Note: It is probably a fair bit more efficient to use an AbstractResourceHolder for each element in collecton style control since the resource manager then performs all the indexing instead of having a middle man.</b></p>
|
||||
*/
|
||||
public class MultiResourceHolder extends com.foundation.view.AbstractMultiResourceHolder {
|
||||
/**
|
||||
* MultiResourceHolder constructor.
|
||||
* @param component The component that will be notified when the resource value changes.
|
||||
*/
|
||||
public MultiResourceHolder(IResourceHolderComponent component) {
|
||||
super(component);
|
||||
}//MultiResourceHolder()//
|
||||
/**
|
||||
* Gets the resource service associated with the application this holder exists under.
|
||||
* @return The application's resource service.
|
||||
*/
|
||||
protected AbstractResourceService getResourceService() {
|
||||
return getComponent().getResourceService();
|
||||
}//getResourceService()//
|
||||
}//MultiResourceHolder//
|
||||
@@ -0,0 +1,45 @@
|
||||
/*
|
||||
* Copyright (c) 2006,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.client.view;
|
||||
|
||||
import com.foundation.tcv.client.application.AbstractClientApplication;
|
||||
import com.foundation.view.IResourceHolderComponent;
|
||||
import com.foundation.view.resource.AbstractResourceService;
|
||||
import com.foundation.view.resource.ResourceReference;
|
||||
|
||||
/**
|
||||
* Used by components that may receive either a static value or a reference to a resource.
|
||||
* This resource association will take the value, register listeners if it is a resource, get the actual value if it is a resource, and notify the component when the value is set or is changed.
|
||||
*/
|
||||
public class ResourceHolder extends com.foundation.view.AbstractResourceHolder {
|
||||
/**
|
||||
* ResourceHolder constructor.
|
||||
* @param component The component that will be notified when the resource value changes.
|
||||
*/
|
||||
public ResourceHolder(IResourceHolderComponent component) {
|
||||
super(component);
|
||||
}//ResourceHolder()//
|
||||
/**
|
||||
* Gets the resource service associated with the application this holder exists under.
|
||||
* @return The application's resource service.
|
||||
*/
|
||||
protected AbstractResourceService getResourceService() {
|
||||
return getComponent().getResourceService();
|
||||
}//getResourceService()//
|
||||
/**
|
||||
* Gets the resource value for the referenced resource.
|
||||
* <p>Note: This will only get the current value and will not provide notification when the value changes.</p>
|
||||
* @param component The component requesting the value.
|
||||
* @return The value for the referenced resource.
|
||||
*/
|
||||
public static Object getResourceValue(IAbstractClientViewComponent component, ResourceReference reference) {
|
||||
AbstractResourceService resourceService = ((AbstractClientApplication) component.getSessionViewController().getSessionController().getApplication()).getResourceService();
|
||||
|
||||
return resourceService.getResourceValue((ResourceReference) reference);
|
||||
}//getResourceValue()//
|
||||
}//ResourceHolder//
|
||||
Reference in New Issue
Block a user