Initial commit from SVN.

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

View File

@@ -0,0 +1,9 @@
<?xml version="1.0" encoding="UTF-8"?>
<classpath>
<classpathentry kind="src" path="src"/>
<classpathentry combineaccessrules="false" kind="src" path="/Common"/>
<classpathentry combineaccessrules="false" kind="src" path="/Foundation"/>
<classpathentry combineaccessrules="false" kind="src" path="/Foundation Web Interfaces"/>
<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER"/>
<classpathentry kind="output" path="bin"/>
</classpath>

View File

@@ -0,0 +1,17 @@
<?xml version="1.0" encoding="UTF-8"?>
<projectDescription>
<name>Foundation Web Application</name>
<comment></comment>
<projects>
</projects>
<buildSpec>
<buildCommand>
<name>org.eclipse.jdt.core.javabuilder</name>
<arguments>
</arguments>
</buildCommand>
</buildSpec>
<natures>
<nature>org.eclipse.jdt.core.javanature</nature>
</natures>
</projectDescription>

View File

@@ -0,0 +1,489 @@
//
// Requires jquery at a minimum.
//
var framework = new BrainstormFramework();
var brainstormFramework = framework;
function BrainstormFramework() {
//The set of open views. The data for each view is an object containing the:
// divId of the view's outer HTML tag; and the
// displayId used to reference the view on the server.
var openViews = [];
//The display ID last used from the client side sequence. The server has its own sequence (negative numbers) that it can issue from.//
var nextDisplayId = 1;
var cleanupStarted = false;
var clientId = 0;
//Set by the application - a function that is called (no parameters) when a call to retrieve a view is not allowed due to the user not being logged in or not having sufficient permissions.
this.disallowedHandler = null;
//
// The function called every N seconds to cleanup after orphaned views.
//
this.cleanup = function() {
if(openViews) {
for(var index = 0; index < openViews.length; index++) {
var next = openViews[index];
if(!$('#frameworkViewContent' + next.id)) {
//Close the view.//
closeViewInternal(next);
//Remove the metadata from the open views array.//
openViews.splice(index--, 1);
}
}
}
//Re-call cleanup every minute.//
setTimeout("brainstormFramework.cleanup()", 60000);
};
//
// Loads the html at the given URL into the container. The container will be emptied of all content prior to loading. Any scripts inside <runonce>..</runonce> tags will be removed and executed as soon as the html is loaded.
// @param containerRef The jquery reference to the container for the html. This can be for example 'body' to reference the body node, or '#body' to reference the node with the ID of 'body'.
// @param url The URL of the html to be loaded.
//
this.append = function(containerRef, url) {
var _this = this;
var container = containerRef ? $(containerRef) : null;
$.ajax({url: url, dataType: 'html', async: false, success: function(data) {
data = _this.extractViewData(data);
if(data.view) {
if(container) {
container.append(data.view);
}
else {
htmlHandler(data.view);
}
}
if(data.script && data.script.length > 0) {
try {
eval(data.script);
} catch(err) {
alert(err);
}
}
}});
}
//
// Loads the html at the given URL into the container. The container will be emptied of all content prior to loading. Any scripts inside <runonce>..</runonce> tags will be removed and executed as soon as the html is loaded.
// @param containerRef The jquery reference to the container for the html. This can be for example 'body' to reference the body node, or '#body' to reference the node with the ID of 'body'.
// @param url The URL of the html to be loaded.
// @param htmlHandler The optional handler to be called to place the html. This may be specified in place of the container ID. The handler will be passed the HTML for the view as a string.
//
this.load = function(containerRef, url, htmlHandler) {
var _this = this;
var container = containerRef ? $(containerRef) : null;
if(container) {
container.empty();
}
$.ajax({url: url, dataType: 'html', async: false, success: function(data) {
data = _this.extractViewData(data);
if(data.view) {
if(container) {
container.html(data.view);
}
else {
htmlHandler(data.view);
}
}
if(data.script && data.script.length > 0) {
try {
eval(data.script);
} catch(err) {
alert(err);
}
}
}});
}
//
// Opens a view given the container for the view and the view controller's class name.
// @param displayContainerId The ID of the container that the view will be appended to.
// @param viewController The java class name for the view controller.
//
this.openView = function(displayContainerId, viewController) {
var displayId = nextDisplayId++;
var divId = "frameworkViewContent" + displayId;
var success = false;
var _this = this;
var displayContainer = $('#' + displayContainerId);
$.ajax({url: "/FrameworkController.java?Request=OpenView&ClientId=" + clientId + "&Controller=" + viewController + "&DisplayId=" + displayId, dataType: 'json', async: false, success: function(data) {
if(data) {
try {
if(data.result == 'client-switch') {
//TODO: Send the client stored view controller metadata to the server, then re-run this call.
//For now we will simply refresh the display, causing us to rebuild everything and get a new client id.//
location.reload();
}
else if(data.result == "success") {
var viewData = _this.extractViewData(data.content);
//Setup the container for the view.//
displayContainer.append("<div id='" + divId + "' displayId='" + displayId + "'>" + "</div>");
//Place the contents of the view.//
$('#' + divId).append(viewData.view);
//TODO: Store any cleanup script?
openViews[openViews.length] = {'divId': divId, 'displayId': displayId};
if(!brainstormFramework.cleanupStarted) {
brainstormFramework.cleanupStarted = true;
brainstormFramework.cleanup();
}
success = true;
//Run the script if one came with the view.//
if(viewData.script.length > 0) {
try {
eval(viewData.script);
} catch(err) {
alert(err);
}
}
}
else if(data.result == "disallowed") {
if(_this.disallowedHandler) _this.disallowedHandler();
}
else {
//TODO: Properly handle this.
alert(data.result);
}
} catch(err) {
alert(err);
}
}
else {
//Error: View creation failed.
}
}});
return success ? displayId : 0;
};
//
// Refreshes the given view by reloading the view's HTML.
// @param frameworkContainer The DIV jquery object that encloses the view's contents. This can be obtained by calling parent() on the jquery object for any root element in the view being refreshed. Note that this is the FRAMEWORK's container which is in turn placed in the container supplied when opening the view.
//
this.refreshView = function(frameworkContainer) {
var frameworkContainerId = frameworkContainer.attr("id");
var _this = this;
frameworkContainer.empty();
for(var index = 0; index < openViews.length; index++) {
var next = openViews[index];
if(next.divId == frameworkContainerId) {
$.ajax({url: "/FrameworkController.java?Request=RefreshView&ClientId=" + clientId + "&DisplayId=" + next.displayId, dataType: 'json', async: false, success: function(data) {
if(data) {
if(data.result == 'client-switch') {
//TODO: Send the client stored view controller metadata to the server, then re-run this call.
//For now we will simply refresh the display, causing us to rebuild everything and get a new client id.//
location.reload();
}
else if(data.result == "success") {
var viewData = _this.extractViewData(data.content);
frameworkContainer.append(viewData.view);
//Run the script if one came with the view.//
if(viewData.script.length > 0) {
try {
eval(viewData.script);
} catch(err) {
alert(err);
}
}
}
}
}});
break;
}
}
};
//
// Gets the URL to call a view for use within a form. Sometimes a form submittal requires a URL string and can't call the BrainstormFramework.callView() method.
// @param displayId The ID number of the view controller (not the view's outer div's id attribute).
// @param query The function to be called on the view controller.
// @param parameters The parameters to add to the call. The parameters should be in URL format with &amp; characters separating parameters. Example: "param1=abc&param2=xyz".
//
this.getCallViewUrl = function(displayId, query, parameters) {
return "/FrameworkController.java?Request=CallView&ClientId=" + clientId + "&DisplayId=" + displayId + "&Query=" + query + (parameters ? "&" + parameters: "");
}
//
// Calls the view controller. If the view controller opens another view, it will use a view id from the server (different range) and return {view: ..., viewId: xx}.
// @param displayId The ID number of the view controller (not the view's outer div's id attribute).
// @param query The function to be called on the view controller.
// @param parameters The parameters to add to the call. The parameters should be in URL format with &amp; characters separating parameters. Example: "param1=abc&param2=xyz".
// @param isAsync Whether the call should be asynchronous.
// @param resultViewContainer The jquery view container where the view resulting from this call will be placed. If this is not specified and a view is returned then a dialog will be created.
// @param resultHandler The function called passing the result if specified. Otherwise the result is returned.
// @return The result if one is generated by the call, otherwise the dialog object if a view is the result, otherwise null.
//
this.callView = function(displayId, query, parameters, isAsync, resultViewContainer, resultHandler) {
var _this = this;
var result;
$.ajax({url: "/FrameworkController.java?Request=CallView&ClientId=" + clientId + "&DisplayId=" + displayId + "&Query=" + query + (parameters ? "&" + parameters: ""), dataType: 'json', cache: false, async: false, success: function(data, statusText, jqXHR) {
if(data) {
if(data.error) {
alert(data.error);
}
if(data.result == 'client-switch') {
//TODO: Send the client stored view controller metadata to the server, then re-run this call.
//For now we will simply refresh the display, causing us to rebuild everything and get a new client id.//
location.reload();
}
else {
try {
if(data.view) {
var viewData = _this.extractViewData(data.view);
if(resultViewContainer) {
var newDisplayId = data.displayId;
var divId = "frameworkViewContent_" + (newDisplayId < 0 ? '_' + Math.abs(newDisplayId) : newDisplayId);
//Save the view data.//
openViews[openViews.length] = {'divId': divId, 'displayId': newDisplayId};
if(!brainstormFramework.cleanupStarted) {
brainstormFramework.cleanupStarted = true;
brainstormFramework.cleanup();
}
//Create the container for the view.//
resultViewContainer.append("<div id='" + divId + "' displayId='" + newDisplayId + "'></div>");
//Append the view HTML to the container.//
$('#' + divId).append(viewData.view);
//TODO: Setup cleanup code.
//Store the result as the div id.//
result = divId;
//Run the script if one came with the view.//
if(viewData.script.length > 0) {
try {
eval(viewData.script);
} catch(err) {
alert(err);
}
}
}
else {
var newDisplayId = data.displayId;
var divId = "frameworkViewContent_" + Math.abs(newDisplayId);
var metadata = viewData.metadata ? $(viewData.metadata).find('metadata') : undefined;
//Save the view data.//
openViews[openViews.length] = {'divId': divId, 'displayId': newDisplayId};
if(!brainstormFramework.cleanupStarted) {
brainstormFramework.cleanupStarted = true;
brainstormFramework.cleanup();
}
//Create the container for the dialog.//
$("body").append("<div id='" + divId + "' displayId='" + newDisplayId + (metadata && metadata.attr('height') && metadata.attr('width') ? "' style='height: " + metadata.attr('height') + "; width: " + metadata.attr('width') + "": "") + "'></div>");
//Populate the dialog contents.//
$('#' + divId).append(viewData.view);
//Open the dialog and cleanup when it closes.//
dialog.open(divId, function() {
brainstormFramework.closeView(divId);
$('#' + divId).remove();
});
//result = divId;
result = dialog;
//Run the script if one came with the view.//
if(viewData.script && viewData.script.length > 0) {
try {
eval(viewData.script);
} catch(err) {
alert(err);
}
}
}
}
if(data.result) {
result = data.result;
}
if(resultHandler) {
resultHandler(result);
}
} catch(err) {
alert(err);
}
}
}
else {
//Error: Call failed.
}
}, error: function(jqXHR, textStatus, errorThrown) {
alert("Failed");
}});
return result;
};
this.extractViewData = function(viewData) {
var data = {script: "", metadata: undefined, view: ""};
var start;
//Remove the escaping that allowed it to be sent as part of a JSON response.//
viewData = this.unescape(viewData);
//Strip out any run-once scripts to be run after loading the html.//
while(viewData.indexOf("<runonce>") != -1) {
//extract the script.//
data.script += viewData.substring(viewData.indexOf("<runonce>") + 9, viewData.indexOf("</runonce>")).replace("<!--", "").replace("//-->", "");
//Remove the script from the view data.//
viewData = viewData.substring(0, viewData.indexOf("<runonce>")) + viewData.substring(viewData.indexOf("</runonce>") + 10);
}
//Detect and remove any metadata.//
if((start = viewData.indexOf('<metadata>')) != -1) {
var end = viewData.indexOf('</metadata>', start + 10);
var metadata = viewData.substring(start, end + 11);
//Remove the metadata from the document.//
viewData = viewData.substring(0, start) + viewData.substring(end + 11);
//Parse the metadata XML.//
data.metadata = $.parseXML(metadata);
}
else if((start = viewData.indexOf('<metadata ')) != -1) {
var end = viewData.indexOf('/>', start + 10);
var metadata = viewData.substring(start, end + 2);
//Remove the metadata from the document.//
viewData = viewData.substring(0, start) + viewData.substring(end + 2);
//Parse the metadata XML.//
data.metadata = $.parseXML(metadata);
}
else if((start = viewData.indexOf('<metadata/>')) != -1) {
viewData = viewData.substring(0, start) + viewData.substring(start + 11);
}
//Strip out any comments.//
while(viewData.indexOf("<!--") != -1) {
//Remove the comment from the view data.//
viewData = viewData.substring(0, viewData.indexOf("<!--")) + viewData.substring(viewData.indexOf("-->") + 3);
}
data.view = viewData;
return data;
}
//
// Closes the view given the view's div ID (the div is created to wrapper the view content and is the only child of the container passed when creating the view, the div's ID is returned by the call to openView(..)).
// @param id The view's div's ID, or display ID.
//
this.closeView = function(id) {
for(var index = 0; index < openViews.length; index++) {
var next = openViews[index];
//Allow the passed id to be either the div ID or the display ID.//
if(next.divId == id || next.displayId == id) {
//Remove the metadata from the open views array.//
openViews.splice(index, 1);
//Close the actual view.//
this.closeViewInternal(next);
break;
}
}
};
//
// Closes the view given the view metadata. The caller is expected to remove the metadata from the view set.
// @param metadata The metadata for the view being removed.
//
this.closeViewInternal = function(metadata) {
//Remove the view HTML from the DOM.//
$('#' + metadata.divId).remove();
//Notify the server that the view has closed.//
$.ajax({url: "/FrameworkController.java?Request=CloseView&ClientId=" + clientId + "&DisplayId=" + metadata.displayId, dataType: 'json', async: true, success: function(data) {
if(data) {
if(data.success == "true") {
//Do nothing?
}
else if(data.result == 'client-switch') {
//Do nothing for now. Ideally this call failing can be ignored since this close view code should remove the server view controller metadata stored on the client which will be used at some future time to restore the client's session on the server.//
}
else {
//TODO:
}
}
else {
//Error: View removal failed.
}
}});
};
//
// Removes escape characters from text.
// @param text The text whose escape characters are to be removed.
//
this.unescape = function(text) {
var result = text.replace(/\x7C1/g, "\\").replace(/\x7C2/g, "'").replace(/\x7C3/g, "\"").replace(/\x7C4/g, "\x0D").replace(/\x7C5/g, "\x09").replace(/\x7C7/g, "&").replace(/\x7C8/g, "<").replace(/\x7C9/g, ">").replace(/\x7C6/g, "\x7C");
return result;
};
//
// Adds escape characters to text.
// @param text The text whose escape characters are to be added. If this is undefined then the result will be an empty string.
//
this.escape = function(text) {
var result;
if(text) {
result = text.replace(/\x0A\x0D/g, "\n").replace(/\x7C/g, "\x7C6").replace(/\\/g, "\x7C1").replace(/\'/g, "\x7C2").replace(/\"/g, "\x7C3").replace(/\n/g, "\x7C4").replace(/\x09/g, "\x7C5").replace(/%/g, "\x7C6").replace(/&/g, "\x7C7").replace(/\x3C/g, "\x7C8").replace(/\x3E/g, "\x7C9");
}
else {
result = "";
}
return result;
};
//Get this view's client id. Each window/tab or refresh requires a new client ID from the server so it can track which set of displays belongs to which view.//
$.ajax({url: "/FrameworkController.java?Request=CreateId", dataType: 'json', async: false, success: function(data) {
if(data) {
clientId = data.result;
}
}});
$(window).onunload = function() {
while(openViews.length > 0) {
var next = openViews[openViews.length - 1];
closeViewInternal(next);
openViews.splice(openViews.length - 1, 1);
}
};
this.cleanup();
}

View File

@@ -0,0 +1,131 @@
package com.foundation.web;
import com.foundation.attribute.ReflectionContext;
import com.foundation.controller.Controller;
import com.foundation.web.controller.HtmlViewController;
/**
* The base class for the HTML View Controller.
* This keeps the sensitive methods hidden from the developer to help prevent errors.
*/
public abstract class BaseHtmlViewController extends BaseWebController {
private int displayId;
/** The manager that will handle creation, destruction, and maintenance of the reflections used by the view. */
private ReflectionContext reflectionContext = null;
private SecureSessionData secureSessionData = null;
private SessionData sessionData = null;
/** The framework session data. */
private WebSession session = null;
/**
* BaseHtmlViewController constructor.
*/
public BaseHtmlViewController() {
}//BaseHtmlViewController()//
/**
* BaseHtmlViewController constructor.
* @param parent The view controller that is creating this view controller.
*/
public BaseHtmlViewController(BaseHtmlViewController parent) {
WebSession session = parent.getSession();
int displayId = session.getNextFrameworkViewId();
setSessionData(parent.getSessionData());
setSecureSessionData(parent.getSecureSessionData());
session.registerFrameworkView(displayId, (HtmlViewController) this);
setDisplayId(displayId);
setSession(session);
}//HtmlViewController()//
/**
* Called by the FrameworkController to initialize a new view.
* @param displayId
* @param session
* @param sessionData
* @param secureSessionData
*/
void initialize(int displayId, WebSession session, SessionData sessionData, SecureSessionData secureSessionData) {
setDisplayId(displayId);
setSession(session);
//Note: We don't just get these from the session since we want to allow non-secure access to the views as needed, in which case the secure session data would not be passed.//
setSessionData(sessionData);
setSecureSessionData(secureSessionData);
}//intialize()//
/**
* Initializes the view controller if possible.
* @param sessionData
* @param secureSessionData
* @return Whether the view could be initialized given the session's credentials.
*/
public abstract boolean initialize();
/**
* Releases the view controller.
*/
public void release() {
if(reflectionContext != null) {
reflectionContext.release();
}//if//
}//release()//
/**
* Gets the ID used by the server and client to index the view managed by this controller.
* @return The ID for the view & controller on client & server.
*/
public int getDisplayId() {
return displayId;
}//getDisplayId()//
/**
* Sets the ID used by the server and client to index the view managed by this controller.
* @param displayId The ID for the view & controller on client & server.
*/
private void setDisplayId(int displayId) {
this.displayId = displayId;
}//setDisplayId()//
/**
* Gets the reflection manager for this view.
* @return The manager that will handle creation, destruction, and maintenance of the reflections used by the view.
*/
public ReflectionContext getReflectionManager() {
return reflectionContext;
}//getReflectionManager()//
/**
* Gets the secure session data for the session that created this view.
* @return The secure data for the session creating this view.
*/
public SecureSessionData getSecureSessionData() {
return secureSessionData;
}//getSecureSessionData()//
/**
* Sets the secure session data for the session that created this view.
* @param secureSessionData The secure data for the session creating this view.
*/
private void setSecureSessionData(SecureSessionData secureSessionData) {
this.secureSessionData = secureSessionData;
}//setSecureSessionData()//
/**
* Gets the session data for the session that created this view.
* @return The data for the session creating this view.
*/
public SessionData getSessionData() {
return sessionData;
}//getSessionData()//
/**
* Sets the session data for the session that created this view.
* @param sessionData The data for the session creating this view.
*/
private void setSessionData(SessionData sessionData) {
this.sessionData = sessionData;
reflectionContext = new ReflectionContext(sessionData.getRequestHandler(), null, null);
}//setSessionData()//
/**
* Gets the session data for the session that created this view.
* @return The session creating this view.
*/
private final WebSession getSession() {
return session;
}//getSession()//
/**
* Sets the session that created this view.
* @param session The session creating this view.
*/
private void setSession(WebSession session) {
this.session = session;
}//setSession()//
}//BaseHtmlViewController//

View File

@@ -0,0 +1,105 @@
package com.foundation.web;
import com.common.util.StringSupport;
import com.foundation.controller.Controller;
public class BaseWebController extends Controller {
public static final String URI_COMPONENT_ENCODED_CHARS = ",/?:@&=+$#";
/**
* Decodes the URI component. This is the same function as in JavaScript.
*/
public static String decodeURIComponent(String content) {
StringBuilder buffer = new StringBuilder(content.length());
for(int index = 0, count = content.length(); index < count; index++) {
char next = content.charAt(index);
if(next == '%') {
char n1 = content.charAt(index + 1);
char n2 = content.charAt(index + 2);
int value = (n1 < 65 ? n1 - 48 : n1 > 90 ? n1 - 87 : n1 - 55) << 4 | (n2 < 65 ? n2 - 48 : n2 > 90 ? n2 - 87 : n2 - 55);
buffer.append((char) value);
}//if//
else {
buffer.append(next);
}//else//
}//for//
return buffer.toString();
}//decodeURIComponent()//
/**
* Encodes the URI component. This is the same function as in JavaScript.
*/
public static String encodeURIComponent(String content) {
StringBuilder buffer = new StringBuilder(content.length() * 3);
for(int index = 0, count = content.length(); index < count; index++) {
char next = content.charAt(index);
if(URI_COMPONENT_ENCODED_CHARS.indexOf(next) != -1) {
int value = (int) next;
buffer.append('%');
buffer.append(StringSupport.hexCharacters[((value >> 4) & 0xF)]);
buffer.append(StringSupport.hexCharacters[(value & 0xF)]);
}//if//
else {
buffer.append(next);
}//else//
}//for//
return buffer.toString();
}//encodeURIComponent()//
/**
* Escapes potential control characters for sending data to the web browser where the browser will use the framework javascript's unescape method to remove the escape characters.
*/
public static String encodeFramework(String content) {
return content == null ? null : content.replace("\r\n", "\n").replace("\r", "\n").replace("|", "|6").replace("\\", "|1").replace("'", "|2").replace("\"", "|3").replace("\n", "|4").replace("\t", "|5").replace("&", "|7").replace("<", "|8").replace(">", "|9");
}//escapeFramework//
/**
* Removes the bar escape characters from content sent in the URL using the escape method provided in the framework javascript.
*/
public static String decodeFrameworkUrlParam(String content) {
return content == null ? null : content.replace("|1", "\\").replace("|2", "'").replace("|3", "\"").replace("|4", "\n").replace("|5", "\t").replace("|6", "%").replace("|7", "&").replace("|8", "<").replace("|9", ">").replace("|6", "|");
}//unescapeFrameworkUrlParam//
/**
* Encodes the content characters that conflict with JSON formatting characters.
*/
public static String encodeJsonParameter(String content) {
//Note: According to official JSON docs, single quotes should not be replaced with escaped single quotes because only double quotes may be used to quote values in the JSON (so there shouldn't be any conflicts using single quotes in the content).//
//.replace("'", "\\'")
return content == null ? null : content.replace("\r\n", "\n").replace("\r", "\n").replace("\\", "&#92;").replace("\"", "\\\"").replace("\n", "\\n");
}//encodeJsonParameter()//
/**
* Encodes the characters that mess up text areas.
*/
public static String encodeTextAreaContent(String content) {
return content == null ? null : content.replace("\r\n", "\n").replace("\r", "\n").replace("&", "&amp;").replace("<", "&lt;").replace(">", "&gt;");
}//escapeTextAreaContent()//
/**
* Removes encoding of text for display in text areas.
*/
public static String decodeTextAreaContent(String content) {
return content == null ? null : content.replace("\r\n", "\n").replace("\r", "\n").replace("&lt;", "<").replace("&gt;", ">").replace("&amp;", "&");
}//decodeTextAreaContent()//
/**
* Utility code to build a proper JSON response to a view request that is handled by the framework javascript on the client <code>(javascript: brainstormFramework.callView(..))</code>.
* <p>This response is designed to be processed by the framework javascript on the client as a response to the callView method.</p>
* @param response The optional request result that is passed along with the new view data.
* @param isError Whether the result is an error. Errors are handled with an alert on the client by the javascript that processes the response (not the application code).
* @return The text for the response.
*/
public JsonContent buildResponse(String response, boolean isError) {
StringBuffer buffer = new StringBuffer(1000);
if(isError) {
buffer.append("{\"error\": \"").append(response).append("\"");
}//if//
else {
buffer.append("{\"result\": \"").append(response).append("\"");
}//else//
return new JsonContent(buffer.append("}").toString());
}//buildResponse()//
}//BaseWebController//

View File

@@ -0,0 +1,29 @@
package com.foundation.web;
import java.io.File;
import com.foundation.web.interfaces.ICachedResource;
/**
* Used to pair the file reference and the file's contents for storage in the cache hashmap.
*/
public class CachedResource implements ICachedResource {
private File file;
private byte[] contents;
private long lastModified;
public CachedResource(File file, byte[] contents) {
this.file = file;
this.contents = contents;
//TODO: It would be a bit more accurate to do this when the contents are read it (assuming we obtain a read lock first).
this.lastModified = file.lastModified();
}//CachedResource()//
public File getFile() {
return file;
}//getFile()//
public byte[] getContents() {
return contents;
}//getContents()//
public long getLastModified() {
return lastModified;
}//getLastModified()//
}//CachedResource//

View File

@@ -0,0 +1,36 @@
package com.foundation.web;
import java.nio.ByteBuffer;
import com.foundation.web.interfaces.IMimeType;
import com.foundation.web.interfaces.IMimeTypeProvider;
/**
* Used to wrapper any response to the web browser via the provided mime type name.
*/
public class CustomContent extends HtmlContent {
private String typeExtension;
/**
* CustomContent constructor.
* @param typeExtension The extension used by the file system (or natural name) for this data type. This will be translated to the mime type via the mime type provider.
* @param content
*/
public CustomContent(String typeExtension, ByteBuffer content) {
super(content);
this.typeExtension = typeExtension != null ? typeExtension : "txt";
}//CustomContent()//
/**
* TextContent constructor.
* @param content
*/
public CustomContent(String content) {
super(content);
}//TextContent()//
/* (non-Javadoc)
* @see com.foundation.web.HtmlContent#getMimeType(com.foundation.web.interfaces.IMimeTypeProvider)
*/
public IMimeType getMimeType(IMimeTypeProvider provider) {
return provider.getMimeType(typeExtension);
}//getMimeType()//
}//CustomContent//

View File

@@ -0,0 +1,489 @@
/*
* Copyright (c) 2008,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.web;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
import java.util.Date;
import java.io.*;
import com.common.debug.Debug;
import com.common.io.StreamSupport;
import com.foundation.web.interfaces.*;
/**
* Encapsulates a file to be returned to a client as a response to a web request.
* The static methods provide assistance in setting up the file content properly and attaching it to the response object.
*/
public class FileContent implements IContent {
/** The file whose content is being accessed. */
private File file = null;
/** The starting position for the download (inclusive). */
private long start = 0;
/** The ending position for the download (inclusive). */
private long end;
/** The channel used to retrieve content from the file. This may be null if the channel is closed, or if the contents of the file were provided as a byte array. */
private FileChannel channel = null;
/** The contents of the file if provided. This allows a caching system to be used while still providing mime information on the file. If null then the channel should be used. */
private byte[] fileContents = null;
/** The offset into the file contents of the next byte to be retrieved. */
private int fileContentsOffset = 0;
/** The directive indicating to the client whether the response should be cached, or null if the default behavior should be used (depends on the MimeType or if provided, the cacheLength attribute). */
private String cacheDirective = null;
/** The date indicating to the client when any cache should become invalid, or null if the default behavior should be used (no date). */
private Date expiresDirective = null;
/** The number of seconds the client is allowed to cache the content, null if the mime type's default cache controls should be used, or one of the CACHE_LENGTH_xxx identifiers indicating how caching should be handled. */
private Integer cacheLength = null;
/** Whether the content should be downloaded, or null if the default behavior should occur based on the mime type. */
private Boolean isDownloaded = null;
/** The custom download name, or if null the file name will be used. */
private String downloadName = null;
/** The extension used to identify the mime type. If null, then the file reference will be used. */
private String extension = null;
/**
* Sets the content using a resource contained in the application's base directory.
* @param IResponse The IResponse whose content is being set.
* @param resource The resource to use as the content of the IResponse.
*/
public static void setContentResource(IResponse response, String resource) {
setContentResource(response, resource, response.getRequest(), null, null, false);
}//setContentResource()//
/**
* Sets the content using a resource contained in the application's base directory.
* @param IResponse The IResponse whose content is being set.
* @param resource The resource to use as the content of the IResponse.
* @param clientCacheDate The date the client supplied for the IResponse it has cached. If non-null, this can be used to send a 304 Not Modified message back to the client if the resource is unchanged.
*/
public static void setContentResource(IResponse response, String resource, Date clientCacheDate) {
setContentResource(response, resource, response.getRequest(), clientCacheDate, null, false);
}//setContentResource()//
/**
* @param resource The resource to use as the content of the IResponse.
* @param IResponse The IResponse whose content is being set.
* @param request The request whose refering URL is used to create a relative path for the resource. If null, then the resource will be relative to the web app's base directory.
*/
public static void setContentResource(IResponse response, String resource, IRequest request) {
setContentResource(response, resource, request, null, null, false);
}//setContentResource()//
/**
* @param resource The resource to use as the content of the IResponse.
* @param IResponse The IResponse whose content is being set.
* @param request The request whose refering URL is used to create a relative path for the resource. If null, then the resource will be relative to the web app's base directory.
* @param isDownload Whether the resource should be downloaded rather than opened in the browser. If this is null then the default action will be taken based on the mime settings.
*/
public static void setContentResource(IResponse response, String resource, IRequest request, Boolean isDownload) {
setContentResource(response, resource, request, null, isDownload, false);
}//setContentResource()//
/**
* Sets the content using a resource contained in the application's base directory.
* @param IResponse The IResponse whose content is being set.
* @param resource The resource to use as the content of the IResponse.
* @param request The request whose refering URL is used to create a relative path for the resource. If null, then the resource will be relative to the web app's base directory.
* @param clientCacheDate The date the client supplied for the IResponse it has cached. If non-null, this can be used to send a 304 Not Modified message back to the client if the resource is unchanged.
* @param isDownload Whether the resource should be downloaded rather than opened in the browser. If this is null then the default action will be taken based on the mime settings.
*/
public static void setContentResource(IResponse response, String resource, IRequest request, Date clientCacheDate, Boolean isDownload) {
setContentResource(response, resource, request, clientCacheDate, isDownload, false);
}//setContentResource()//
/**
* Sets the content using a resource contained in the application's base directory.
* @param IResponse The IResponse whose content is being set.
* @param resource The resource to use as the content of the IResponse.
* @param request The request whose refering URL is used to create a relative path for the resource. If null, then the resource will be relative to the web app's base directory.
* @param clientCacheDate The date the client supplied for the IResponse it has cached. If non-null, this can be used to send a 304 Not Modified message back to the client if the resource is unchanged.
* @param isDownload Whether the resource should be downloaded rather than opened in the browser. If this is null then the default action will be taken based on the mime settings.
* @param infinateCache Whether the client should cache the resource indefinately. This is useful for overriding the default caching limits for things like images that will never change.
*/
public static void setContentResource(IResponse response, String resource, IRequest request, Date clientCacheDate, Boolean isDownload, boolean infinateCache) {
File file = null;
IWebApplication application = response.getApplication();
boolean success = false;
file = new File(application.getBaseDirectory(), resource);
if(file.exists()) {
success = setContentResource(application, response, request, clientCacheDate, isDownload, infinateCache, file);
}//if//
if(!success && application.getExternalBaseDirectory() != null) {
file = new File(application.getExternalBaseDirectory(), resource);
if(file.exists()) {
success = setContentResource(application, response, request, clientCacheDate, isDownload, infinateCache, file);
}//if//
}//if//
if(!success && application.getCacheBaseDirectory() != null) {
file = new File(application.getCacheBaseDirectory(), resource);
if(file.exists()) {
success = setContentResource(application, response, request, clientCacheDate, isDownload, infinateCache, file);
}//if//
}//if//
if(!success) {
response.setError(IResponse.ERROR_TYPE_RESOURCE_NOT_FOUND);
}//if//
}//setContentResource()//
/**
* Sets the content using a resource contained in the application's base directory.
* @param IResponse The IResponse whose content is being set.
* @param resource The resource to use as the content of the IResponse.
* @param request The request whose refering URL is used to create a relative path for the resource. If null, then the resource will be relative to the web app's base directory.
* @param clientCacheDate The date the client supplied for the IResponse it has cached. If non-null, this can be used to send a 304 Not Modified message back to the client if the resource is unchanged.
* @param isDownload Whether the resource should be downloaded rather than opened in the browser. If this is null then the default action will be taken based on the mime settings.
* @param infinateCache Whether the client should cache the resource indefinately. This is useful for overriding the default caching limits for things like images that will never change.
*/
public static void setContentResource(IResponse response, File file, IRequest request, Date clientCacheDate, Boolean isDownload, boolean infinateCache) {
IWebApplication application = response.getApplication();
boolean success = false;
if(file.exists()) {
success = setContentResource(application, response, request, clientCacheDate, isDownload, infinateCache, file);
}//if//
if(!success) {
response.setError(IResponse.ERROR_TYPE_RESOURCE_NOT_FOUND);
}//if//
}//setContentResource()//
/**
* Performs the actual work of setting the content resource, allowing for multiple file locations to be used by the caller.
*/
private static boolean setContentResource(IWebApplication application, IResponse response, IRequest request, Date clientCacheDate, Boolean isDownload, boolean infinateCache, File file) {
boolean result = false;
String canonicalPath = null;
ICachedResource cache = null;
if(application.isCachingResources()) {
try {
canonicalPath = file.getCanonicalPath();
cache = application.getCachedResource(canonicalPath);
}//try//
catch(Throwable e) {}
}//if//
if(cache != null) {
if((clientCacheDate != null) && (!new Date(file.lastModified() & 0xFFFFFFFFFFFF0000L).after(clientCacheDate))) {
//Send a 304 Not Modified message to the client.//
response.setError(IResponse.ERROR_TYPE_RESOURCE_NOT_MODIFIED);
}//if//
else {
//boolean includeRange = request.getUnmodifiedSinceDate() != null && request.getUnmodifiedSinceDate().getTime() >= cache.getLastModified();
boolean includeRange = request.getRange() != null;
result = true;
response.setContent(new FileContent(cache.getFile(), cache.getContents(), includeRange ? request.getLowerRange() : null, includeRange ? request.getUpperRange() : null));
}//else//
}//if//
else if(file.exists() && file.canRead() && file.isFile()) {
result = true;
try {
FileContent fileContent = null;
if((clientCacheDate != null) && (!new Date(file.lastModified() & 0xFFFFFFFFFFFF0000L).after(clientCacheDate))) {
//Send a 304 Not Modified message to the client.//
response.setError(IResponse.ERROR_TYPE_RESOURCE_NOT_MODIFIED);
}//if//
else if(canonicalPath != null && file.length() < application.getMaxCachedResourceSize()) {
boolean includeRange;
//TODO: Should read the bytes with a read lock & obtain the update TS at the same time for accuracy.
byte[] bytes = StreamSupport.readBytes(file);
application.setCachedResource(canonicalPath, file, bytes);
//includeRange = request.getUnmodifiedSinceDate() != null && (request.getUnmodifiedSinceDate().getTime() & 0xFFFFFFFFFFFF0000L) >= (file.lastModified() & 0xFFFFFFFFFFFF0000L);
includeRange = request.getRange() != null;
response.setContent(fileContent = new FileContent(file, bytes, includeRange ? request.getLowerRange() : null, includeRange ? request.getUpperRange() : null));
}//else if//
else {
//TODO: It would be nice to be able to validate as the download occurs that the file hasn't been updated.
//boolean includeRange = request.getUnmodifiedSinceDate() != null && (request.getUnmodifiedSinceDate().getTime() & 0xFFFFFFFFFFFF0000L) >= (file.lastModified() & 0xFFFFFFFFFFFF0000L);
boolean includeRange = request.getRange() != null;
FileContent content = new FileContent(file, includeRange ? request.getLowerRange() : null, includeRange ? request.getUpperRange() : null);
if(isDownload != null) {
content.setIsDownloaded(isDownload);
//content.setCacheLength(new Integer(MimeType.CACHE_LENGTH_NEVER_CACHE));
}//if//
response.setContent(fileContent = content);
}//else//
if(infinateCache && fileContent != null) {
fileContent.setCacheLength(IResponse.INFINATE_CACHE_LENGTH); //6 months in seconds.//
fileContent.setExpiresDirective(new Date(new Date().getTime() + (IResponse.INFINATE_CACHE_LENGTH.intValue() * 1000)));
}//if//
}//try//
catch(Throwable e) {
Debug.log(e);
//Display an error.//
response.setError(IResponse.ERROR_TYPE_RESOURCE_NOT_FOUND);
}//catch//
}//if//
return result;
}//setContentResource()//
/**
* FileContent constructor.
* @param file The file whose contents will be streamed to the client.
*/
public FileContent(File file) throws FileNotFoundException, IOException {
this(file, null, null);
}//FileContent()//
/**
* FileContent constructor.
* @param file The file being represented.
* @param bytes The contents of the file. Use this method if the contents are already cached in memory.
*/
public FileContent(File file, byte[] bytes) {
this(file, bytes, null, null);
}//FileContent()//
/**
* FileContent constructor.
* @param bytes The contents of the file. Use this method if the contents are already cached in memory.
* @param name The file name including extension (used to identify the mime type, and used as the download name).
*/
public FileContent(byte[] bytes, String name) {
this(bytes, name, null, null);
}//FileContent()//
/**
* FileContent constructor.
* @param file The file whose contents will be streamed to the client.
*/
public FileContent(File file, Long start, Long end) throws FileNotFoundException, IOException {
this.file = file;
this.channel = new FileInputStream(file).getChannel();
if(start != null) {
this.start = start.longValue();
this.channel.position(start.longValue());
}//if//
this.end = end != null ? end.longValue() : file.length() - 1;
// Debug.log("Sending file " + file.getPath() + " starting at " + this.channel.position() + "; ending at " + this.end);
}//FileContent()//
/**
* FileContent constructor.
* @param file The file being represented.
* @param bytes The contents of the file. Use this method if the contents are already cached in memory.
*/
public FileContent(File file, byte[] bytes, Long start, Long end) {
this.file = file;
this.fileContents = bytes;
if(start != null) {
this.start = start.longValue();
fileContentsOffset = start.intValue();
}//if//
this.end = end != null ? end.longValue() : bytes.length - 1;
// Debug.log("Sending cached file " + file.getPath() + " starting at " + this.fileContentsOffset + "; ending at " + this.end);
}//FileContent()//
/**
* FileContent constructor.
* @param bytes The contents of the file. Use this method if the contents are already cached in memory.
* @param name The file name including extension (used to identify the mime type, and used as the download name).
*/
public FileContent(byte[] bytes, String name, Long start, Long end) {
this.fileContents = bytes;
this.extension = name.substring(name.lastIndexOf('.') + 1);
this.downloadName = name;
if(start != null) {
this.start = start.longValue();
fileContentsOffset = start.intValue();
}//if//
this.end = end != null ? end.longValue() : bytes.length - 1;
// Debug.log("Sending cached (2) file " + this.downloadName + " starting at " + this.fileContentsOffset + "; ending at " + this.end);
}//FileContent()//
/* (non-Javadoc)
* @see com.foundation.web.interfaces.IContent#release()
*/
public void release() {
if(channel != null) {
try {
channel.close();
}//try//
catch(Throwable e) {}
channel = null;
}//if//
}//release()//
/* (non-Javadoc)
* @see com.foundation.web.interfaces.IContent#get(java.nio.ByteBuffer)
*/
public int get(ByteBuffer buffer) {
int result = -1;
if(fileContents != null) {
if(fileContentsOffset < fileContents.length) {
int length = (int) Math.min(end + 1, Math.min(buffer.remaining(), fileContents.length - fileContentsOffset));
// Debug.log("Sending " + length + " cached bytes starting at position " + fileContentsOffset + " of " + (file != null ? file.getPath() : downloadName));
buffer.put(fileContents, fileContentsOffset, length);
fileContentsOffset += length;
result = length;
}//if//
}//if//
else if(channel != null) {
boolean closeChannel = false;
try {
buffer.limit((int) Math.min(buffer.capacity(), end + 1 - channel.position() + buffer.position()));
// long debugPosition = channel.position();
if(buffer.limit() == 0 || (result = channel.read(buffer)) == -1) {
closeChannel = true;
}//if//
// Debug.log("Sending " + result + " file bytes (expected: " + buffer.limit() + ") starting at position " + debugPosition + " of " + file.getPath());
}//try//
catch(IOException e) {
closeChannel = true;
Debug.log(file != null ? "Failed to read the file: " + file.getAbsolutePath() : "Failed while dealing with a byte buffer.", e);
}//catch//
if(closeChannel) {
try {
channel.close();
}//try//
catch(Throwable e) {}
channel = null;
// Debug.log("File download complete: " + file.getPath());
}//if//
}//else if//
return result;
}//get()//
/* (non-Javadoc)
* @see com.foundation.web.interfaces.IContent#getSize()
*/
public long getSize() {
//Note: Can't use the file size when using a cached resource since it may differ from the cached value.//
return (fileContents != null ? fileContents.length : file.length()) - start;
}//getSize()//
/* (non-Javadoc)
* @see com.foundation.web.interfaces.IContent#getStart()
*/
public long getStart() {
return start;
}//getStart()//
/* (non-Javadoc)
* @see com.foundation.web.interfaces.IContent#getEnd()
*/
public long getEnd() {
return end;
}//getEnd()//
/* (non-Javadoc)
* @see com.foundation.web.interfaces.IContent#reset()
*/
public void reset() {
if(channel != null) {
try {
channel.close();
}//try//
catch(Throwable e) {}
}//if//
try {
channel = new FileInputStream(file).getChannel();
}//try//
catch(Throwable e) {
Debug.log(e);
}//catch//
}//reset()//
/* (non-Javadoc)
* @see com.foundation.web.interfaces.IContent#getLastModifiedDate()
*/
public Date getLastModifiedDate() {
return file != null ? new Date(file.lastModified()) : new Date();
}//getLastModifiedDate()//
/* (non-Javadoc)
* @see com.foundation.web.interfaces.IContent#getMimeType(com.foundation.web.interfaces.IMimeTypeProvider)
*/
public IMimeType getMimeType(IMimeTypeProvider provider) {
return extension == null ? provider.getMimeType(file) : provider.getMimeType(extension);
}//getMimeType()//
/**
* Sets the file's extension. Useful if only the file's contents are provided.
* @param extension The file extension identifying the mime type. Should not include the separator.
*/
public void setExtension(String extension) {
this.extension = extension;
}//getMimeType()//
/* (non-Javadoc)
* @see com.foundation.web.interfaces.IContent#getCacheDirective()
*/
public String getCacheDirective() {
return cacheDirective;
}//getCacheDirective()//
/**
* Sets the custom cache directive to be used when sending the response to the client.
* @param cacheDirective The directive indicating to the client whether the response should be cached, or null if the default behavior should be used (depends on the MimeType or if provided, the cacheLength attribute).
*/
public void setCacheDirective(String cacheDirective) {
this.cacheDirective = cacheDirective;
}//getCacheDirective()//
/* (non-Javadoc)
* @see com.foundation.web.interfaces.IContent#getExpiresDirective()
*/
public Date getExpiresDirective() {
return expiresDirective;
}//getExpiresDirective()//
/**
* Sets the default expires date directive to be used when sending the response to the client.
* @param expiresDirective The date indicating to the client when any cache should become invalid, or null if the default behavior should be used (no date).
*/
public void setExpiresDirective(Date expiresDirective) {
this.expiresDirective = expiresDirective;
}//setExpiresDirective()//
/* (non-Javadoc)
* @see com.foundation.web.interfaces.IContent#getCacheLength()
*/
public Integer getCacheLength() {
return cacheLength;
}//getCacheLength()//
/**
* Sets the cache length in terms of seconds, or one of the CACHE_LENGTH_xxx identifiers defined by IContent.
* <p>Note: This is an easier way of controlling caching than a custom CacheDirective. The custom cache directive will override this if provided.</p>
* @param cacheLength The number of seconds the client is allowed to cache the content, null if the mime type's default cache controls should be used, or one of the CACHE_LENGTH_xxx identifiers indicating how caching should be handled.
*/
public void setCacheLength(Integer cacheLength) {
this.cacheLength = cacheLength;
}//setCacheLength()//
/* (non-Javadoc)
* @see com.foundation.web.interfaces.IContent#getIsDownloaded()
*/
public Boolean getIsDownloaded() {
return isDownloaded;
}//getIsDownloaded()//
/**
* Gets whether the content should be downloaded versus displayed in the browser.
* @param isDownloaded Whether the content should be downloaded, or null if the default behavior should occur based on the mime type.
*/
public void setIsDownloaded(Boolean isDownloaded) {
this.isDownloaded = isDownloaded;
}//setIsDownloaded()//
/* (non-Javadoc)
* @see com.foundation.web.interfaces.IContent#getDownloadName()
*/
public String getDownloadName() {
return downloadName == null ? file.getName() : downloadName;
}//getDownloadName()//
/**
* Sets the download name used when downloading the content (versus displaying it in the browser).
* @param downloadName The custom download name, or if null the file name will be used.
*/
public void setDownloadName(String downloadName) {
this.downloadName = downloadName;
}//setDownloadName()//
}//FileContent//

View File

@@ -0,0 +1,238 @@
/*
* Copyright (c) 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.web;
import java.io.File;
import javax.net.ssl.SSLContext;
import com.foundation.event.IRequestHandler;
import com.foundation.web.interfaces.*;
/**
* Forwards from the given domains to another domain.
*/
public class ForwardingDomain implements IWebApplication {
/** The name used to map the web application to its self when reloading a webapp bundle. */
private String name = null;
/** The container for this application. This will be set once the application is added to the server and should no longer be modified. */
private IWebApplicationContainer webApplicationContainer = null;
/** The domains that this web server accepts. */
private String[] domains = null;
/** The services to listen for connections on. Typical service names are: "HTTP" and "SSL". Names actually used are application dependant and must match service names used when setting up the web server. */
private String[] services = null;
/** The domain that this web server application will forward all requests to. */
private String toDomain = null;
/**
* ForwardingDomain constructor.
* @param The name used to map the web application to its self when reloading a webapp bundle. This name should not change between versions of the application.
* @param services The services to listen for connections on. Typical service names are: "HTTP" and "SSL". Names actually used are application dependant and must match service names used when setting up the web server.
* @param domains The array of domains being serviced. For example: <code>new String[] {"www.mydomain.com"}</code>
* @param toDomain The domain portion of the URL that all requests will be redirected to. For example: <code>http://www.therealdomain.com</code>
*/
public ForwardingDomain(String name, String[] services, String[] domains, String toDomain) {
this.name = name;
this.domains = domains;
this.toDomain = toDomain;
this.services = services;
}//ForwardingDomain()//
/* (non-Javadoc)
* @see com.foundation.web.interfaces.IWebApplication#getName()
*/
public String getName() {
return name;
}//getName()//
/* (non-Javadoc)
* @see com.foundation.web.interfaces.IWebApplication#getCompressResponses()
*/
public boolean getCompressResponses() {
return false;
}//getCompressResponses()//
/* (non-Javadoc)
* @see com.foundation.web.interfaces.IWebApplication#getDefaultPackageName()
*/
public String getDefaultPackageName() {
return null;
}//getDefaultPackageName()//
/* (non-Javadoc)
* @see com.foundation.web.interfaces.IWebApplication#setCacheCuttoffSize(long)
*/
public void setCacheCuttoffSize(long cacheCutoffSize) {
}//setCacheCuttoffSize()//
/* (non-Javadoc)
* @see com.foundation.web.interfaces.IWebApplication#clearCaches()
*/
public void clearCaches() {
}//clearCaches()//
/* (non-Javadoc)
* @see com.foundation.web.interfaces.IWebApplication#release()
*/
public void release() {
}//release()//
/* (non-Javadoc)
* @see com.foundation.web.interfaces.IWebApplication#serializeSessions()
*/
public File serializeSessions() {
return null;
}//serializeSessions()//
/* (non-Javadoc)
* @see com.foundation.web.interfaces.IWebApplication#deserializeSessions(java.io.File)
*/
public boolean deserializeSessions(File sessionFile) {
return true;
}//deserializeSessions()//
/* (non-Javadoc)
* @see com.foundation.web.interfaces.IWebApplication#getWebApplicationContainer()
*/
public IWebApplicationContainer getWebApplicationContainer() {
return webApplicationContainer;
}//getWebApplicationContainer()//
/* (non-Javadoc)
* @see com.foundation.web.interfaces.IWebApplication#completeInitialization(com.foundation.web.interfaces.IWebApplicationContainer)
*/
public void completeInitialization(IWebApplicationContainer webApplicationContainer) {
if(webApplicationContainer == null) {
throw new IllegalArgumentException("Must provide a web server reference.");
}//if//
else if(this.webApplicationContainer != null) {
throw new IllegalArgumentException("Cannot complete the initialization of a web application twice.");
}//else if//
this.webApplicationContainer = webApplicationContainer;
}//completeInitialization()//
/* (non-Javadoc)
* @see com.foundation.web.server.IWebApplication#getCachedResource(java.lang.String)
*/
public ICachedResource getCachedResource(String canonicalPath) {
return null;
}//getCachedResource()//
/* (non-Javadoc)
* @see com.foundation.web.server.IWebApplication#getMaxCachedResourceSize()
*/
public long getMaxCachedResourceSize() {
return 0;
}//getMaxCachedResourceSize()//
/* (non-Javadoc)
* @see com.foundation.web.server.IWebApplication#getMimeTypeProvider()
*/
public IMimeTypeProvider getMimeTypeProvider() {
return null;
}//getMimeTypeProvider()//
/* (non-Javadoc)
* @see com.foundation.web.server.IWebApplication#isCachingResources()
*/
public boolean isCachingResources() {
return false;
}//isCachingResources()//
/* (non-Javadoc)
* @see com.foundation.web.server.IWebApplication#setCachedResource(java.lang.String, java.io.File, byte[])
*/
public void setCachedResource(String canonicalPath, File file, byte[] contents) {
}//setCachedResource()//
/* (non-Javadoc)
* @see com.foundation.web.interfaces.IWebApplication#createSecureSession(com.foundation.web.interfaces.ISession)
*/
public void createSecureSession(ISession session) {
}//createSecureSession()//
/* (non-Javadoc)
* @see com.foundation.web.interfaces.IWebApplication#createSession()
*/
public ISession createSession() {
return null;
}//createSession()//
/* (non-Javadoc)
* @see com.foundation.web.interfaces.IWebApplication#recreateSession(java.lang.String)
*/
public ISession recreateSession(String sessionId) {
return null;
}//recreateSession()//
/* (non-Javadoc)
* @see com.foundation.web.interfaces.IWebApplication#getSessionData(com.foundation.web.interfaces.ISession)
*/
public byte[] getSessionData(ISession session) {
return null;
}//getSessionData()//
/* (non-Javadoc)
* @see com.foundation.web.interfaces.IWebApplication#getMaxMessageSize(com.foundation.web.interfaces.IRequest, java.lang.Object, java.lang.Object)
*/
public int getMaxMessageSize(IRequest request, Object sessionData, Object secureSessionData) {
return 0;
}//getMaxMessageSize()//
/* (non-Javadoc)
* @see com.foundation.web.server.IWebServerApplication#getBaseDirectory()
*/
public File getBaseDirectory() {
return null;
}//getBaseDirectory()//
/* (non-Javadoc)
* @see com.foundation.web.interfaces.IWebApplication#getExternalBaseDirectory()
*/
public File getExternalBaseDirectory() {
return null;
}//getExternalBaseDirectory()//
/* (non-Javadoc)
* @see com.foundation.web.interfaces.IWebApplication#getCacheBaseDirectory()
*/
public File getCacheBaseDirectory() {
return null;
}//getCacheBaseDirectory()//
/* (non-Javadoc)
* @see com.foundation.web.interfaces.IWebApplication#initiateOpenIdAuth(java.lang.String)
*/
public String initiateOpenIdAuth(String userId) {
return null;
}//initiateOpenIdAuth()//
/* (non-Javadoc)
* @see com.foundation.web.server.IWebServerApplication#getDomains()
*/
public String[] getDomains() {
return domains;
}//getDomains()//
/* (non-Javadoc)
* @see com.foundation.web.server.IWebApplication#getServices()
*/
public String[] getServices() {
return services;
}//getServices()//
/* (non-Javadoc)
* @see com.foundation.web.server.IWebApplication#getSslContext(java.lang.String)
*/
public SSLContext getSslContext(String domain) {
return null;
}//getSslContext()//
/* (non-Javadoc)
* @see com.foundation.web.server.IWebServerApplication#getErrorContent(int)
*/
public IContent getErrorContent(int errorCode) {
return null;
}//getErrorContent()//
/* (non-Javadoc)
* @see com.foundation.web.server.IWebServerApplication#getErrorHeader(int)
*/
public String getErrorHeader(int errorCode) {
return null;
}//getErrorHeader()//
/* (non-Javadoc)
* @see com.foundation.web.server.IWebServerApplication#getSession(java.lang.String)
*/
public ISession getSession(String sessionId) {
return null;
}//getSession()//
/* (non-Javadoc)
* @see com.foundation.web.interfaces.IWebApplication#processRequest(com.foundation.web.interfaces.IRequest, com.foundation.web.interfaces.IResponse, com.foundation.web.interfaces.ISession, boolean, boolean)
*/
public void processRequest(IRequest request, IResponse response, ISession session, boolean isSecure, boolean clientHadBadSession) {
response.setForwardUri(toDomain + request.getUri());
}//processRequest()//
/* (non-Javadoc)
* @see com.foundation.web.interfaces.IWebApplication#retainWholeRequestBuffer(com.foundation.web.interfaces.IRequest)
*/
public boolean retainWholeRequestBuffer(IRequest request) {
return false;
}//retainWholeRequestBuffer()//
}//ForwardingDomain//

View File

@@ -0,0 +1,141 @@
package com.foundation.web;
import java.io.File;
import com.common.debug.Debug;
import com.common.io.StreamSupport;
import com.common.thread.IRunnable;
import com.common.util.LiteHashMap;
import com.foundation.web.controller.HtmlViewController;
import com.foundation.web.controller.WebRequestController;
import com.foundation.web.interfaces.IRequest;
import com.foundation.web.interfaces.IResponse;
/**
* Used to automate creating views in the HTML using stateful view controllers.
*/
public final class FrameworkController extends WebRequestController {
private static final Integer INVALID_CLIENT_ID = new Integer(0);
/* (non-Javadoc)
* @see com.foundation.web.IWebRequestHandler#processRequest(com.foundation.web.interfaces.IRequest, com.foundation.web.interfaces.IResponse, com.foundation.web.SessionData, com.foundation.SecureSessionData)
*/
public void processRequest(IRequest request, IResponse response, SessionData sessionData, SecureSessionData secureSessionData) {
String result = null;
//Note that all calls to this method within the context of a single session are single threaded - the session's request handler is used by the web application to thread the requests on the session's thread.//
try {
String command = request.getParameterAsString("Request", null, true, true);
WebSession session = (WebSession) request.getSession();
if(command.equalsIgnoreCase("CreateId")) {
int clientId = session.getNextClientId();
session.setCurrentClientId(clientId);
result = "{\"result\": " + clientId + " }";
}//if//
else {
int clientId = request.getParameterAsInteger("ClientId", INVALID_CLIENT_ID).intValue();
if(clientId == 0) {
//TODO: Report an error.
}//if//
else if(clientId != session.getCurrentClientId()) {
//Set the current client ID as this one.//
session.setCurrentClientId(clientId);
//Send a response to the client asking for the client view metadata so that the view controllers can be re-initialized to service their requests.//
//The client should then re-send the current request.//
result = "{\"result\":\"client-switch\"}";
}//else if//
else {
if(command.equals("CallView")) {
Integer id = request.getParameterAsInteger("DisplayId", null);
HtmlViewController controller = session.getFrameworkView(id.intValue());
if(controller != null) {
controller.processRequest(request.getParameterAsString("Query", null, true, true), request, response, sessionData, secureSessionData);
}//if//
else {
Debug.log(new RuntimeException("Failed to retrieve a valid controller."));
result = "{\"result\":\"not-found\"}";
}//else//
}//if//
else if(command.equals("RefreshView")) {
Integer id = request.getParameterAsInteger("DisplayId", null);
HtmlViewController controller = session.getFrameworkView(id.intValue());
if(controller != null) {
result = "{\"result\":\"success\", \"content\":\"" + HtmlViewController.encodeFramework(controller.getView()) + "\"}";
}//if//
else {
Debug.log(new RuntimeException("Failed to retrieve a valid controller while refreshing."));
result = "{\"result\":\"not-found\"}";
}//else//
}//else if//
else if(command.equals("OpenView")) {
String controllerName = request.getParameterAsString("Controller", null);
Integer id = request.getParameterAsInteger("DisplayId", null);
Class controllerClass = Class.forName(controllerName);
if(controllerClass == null) {
result = "{\"result\":\"missing-class\"}";
}//if//
else if(HtmlViewController.class.isAssignableFrom(controllerClass)) {
if(id != null) {
HtmlViewController controller = (HtmlViewController) controllerClass.newInstance();
//Initialize the framework components of the view.//
((BaseHtmlViewController) controller).initialize(id.intValue(), session, sessionData, secureSessionData);
//If the view can be initialized then place it in the map and return success and the view's content.//
//Ensure that the request is run on the view's thread.//
if(controller.initialize()) {
HtmlViewController old = session.unregisterFrameworkView(id.intValue());
//Note: It is possible for the client and server to become out of sync if the client is refreshed because the server will retain all the view controller info, but the client will have no knowledge of the views.//
if(old != null) {
old.release();
}//if//
//Map the view to the id for later retrieval.//
session.registerFrameworkView(id.intValue(), controller);
//Ensure that the view creation is run on the view's thread in case reflections need to be generated.//
result = "{\"result\":\"success\", \"content\":\"" + HtmlViewController.encodeFramework(controller.getView()) + "\"}";
}//if//
else {
controller.release();
result = "{\"result\":\"disallowed\"}";
}//else//
}//if//
else {
result = "{\"result\":\"System Failure: Missing display ID.\"}";
}//else//
}//else if//
else {
result = "{\"result\":\"invalid-class\"}";
}//else//
}//else if//
else if(command.equalsIgnoreCase("CloseView")) {
Integer id = request.getParameterAsInteger("DisplayId", null);
HtmlViewController controller = session.unregisterFrameworkView(id.intValue());
if(controller != null) {
controller.release();
result = "{\"result\":\"success\"}";
}//if//
else {
//Note: This might occur normally if the client looses its session.//
result = "{\"result\":\"not-found\"}";
}//else//
}//else if//
}//else//
}//else//
}//try//
catch(Throwable e) {
Debug.log(e);
}//catch//
if(result != null) {
response.setContent(new JsonContent(result));
}//if//
}//processRequest()//
}//FrameworkController//

View File

@@ -0,0 +1,188 @@
/*
* Copyright (c) 2008,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.web;
import java.nio.ByteBuffer;
import java.nio.CharBuffer;
import java.nio.charset.Charset;
import java.nio.charset.CharsetEncoder;
import java.util.Date;
import com.common.debug.Debug;
import com.foundation.web.interfaces.*;
/**
* This content class is designed for use by code that generates a custom HTML response to a request.
* The class has a default caching scheme of no-store (never cache).
*/
public class HtmlContent implements IContent {
private static final Charset charset = Charset.forName("utf8"); //us-ascii
private static final CharsetEncoder encoder = charset.newEncoder();
private ByteBuffer content = null;
private long size = 0;
/** The directive indicating to the client whether the response should be cached, or null if the default behavior should be used (depends on the MimeType or if provided, the cacheLength attribute). */
private String cacheDirective = null;
/** The date indicating to the client when any cache should become invalid, or null if the default behavior should be used (no date). */
private Date expiresDirective = null;
/** The number of seconds the client is allowed to cache the content, null if the mime type's default cache controls should be used, or one of the CACHE_LENGTH_xxx identifiers indicating how caching should be handled. */
private Integer cacheLength = IContent.CACHE_LENGTH_NEVER_CACHE;
/** Whether the content should be downloaded, or null if the default behavior should occur based on the mime type. */
private Boolean isDownloaded = null;
/** The custom download name, or if null the file name will be used. */
private String downloadName = null;
/**
* HtmlContent constructor.
* @param content The text containing the content to be sent.
*/
public HtmlContent(String content) {
try {
this.content = encoder.encode(CharBuffer.wrap(content.toCharArray()));
//this.content = ByteBuffer.allocate(content.length());
//for(int index = 0, count = content.length(); index < count; index++) this.content.put((byte) content.charAt(index));
this.size = this.content.limit();
}//try//
catch(Throwable e) {
Debug.log(e);
}//catch//
}//HtmlContent()//
/**
* HtmlContent constructor.
* @param The byte buffer containing the encoded content.
*/
public HtmlContent(ByteBuffer content) {
try {
this.content = content;
this.size = this.content.limit();
}//try//
catch(Throwable e) {
Debug.log(e);
}//catch//
}//HtmlContent()//
/* (non-Javadoc)
* @see com.foundation.web.IContent#release()
*/
public void release() {
}//release()//
/* (non-Javadoc)
* @see com.foundation.web.IContent#get(java.nio.ByteBuffer)
*/
public int get(ByteBuffer buffer) {
int result = -1;
if(content != null && content.hasRemaining()) {
result = Math.min(content.remaining(), buffer.remaining());
buffer.put(content.array(), content.arrayOffset() + content.position(), result);
content.position(content.position() + result);
}//if//
return result;
}//get()//
/* (non-Javadoc)
* @see com.foundation.web.IContent#getLastModifiedDate()
*/
public Date getLastModifiedDate() {
return null;
}//getLastModifiedDate()//
/* (non-Javadoc)
* @see com.foundation.web.IContent#getMimeType(com.foundation.web.IMimeTypeProvider)
*/
public IMimeType getMimeType(IMimeTypeProvider provider) {
return provider.getMimeType("html");
}//getMimeType()//
/* (non-Javadoc)
* @see com.foundation.web.IContent#getSize()
*/
public long getSize() {
return size;
}//getSize()//
/* (non-Javadoc)
* @see com.foundation.web.interfaces.IContent#getStart()
*/
public long getStart() {
return 0;
}//getStart()//
/* (non-Javadoc)
* @see com.foundation.web.interfaces.IContent#getEnd()
*/
public long getEnd() {
return size - 1;
}//getEnd()//
/* (non-Javadoc)
* @see com.foundation.web.IContent#reset()
*/
public void reset() {
content.position(0);
}//reset()//
/* (non-Javadoc)
* @see com.foundation.web.IContent#getCacheDirective()
*/
public String getCacheDirective() {
return cacheDirective;
}//getCacheDirective()//
/**
* Sets the custom cache directive to be used when sending the response to the client.
* @param cacheDirective The directive indicating to the client whether the response should be cached, or null if the default behavior should be used (depends on the MimeType or if provided, the cacheLength attribute).
*/
public void setCacheDirective(String cacheDirective) {
this.cacheDirective = cacheDirective;
}//getCacheDirective()//
/* (non-Javadoc)
* @see com.foundation.web.IContent#getExpiresDirective()
*/
public Date getExpiresDirective() {
return expiresDirective;
}//getExpiresDirective()//
/**
* Sets the default expires date directive to be used when sending the response to the client.
* @param expiresDirective The date indicating to the client when any cache should become invalid, or null if the default behavior should be used (no date).
*/
public void setExpiresDirective(Date expiresDirective) {
this.expiresDirective = expiresDirective;
}//setExpiresDirective()//
/* (non-Javadoc)
* @see com.foundation.web.IContent#getCacheLength()
*/
public Integer getCacheLength() {
return cacheLength;
}//getCacheLength()//
/**
* Sets the cache length in terms of seconds, or one of the CACHE_LENGTH_xxx identifiers defined by IContent.
* <p>Note: This is an easier way of controlling caching than a custom CacheDirective. The custom cache directive will override this if provided.</p>
* @param cacheLength The number of seconds the client is allowed to cache the content, null if the mime type's default cache controls should be used, or one of the CACHE_LENGTH_xxx identifiers indicating how caching should be handled.
*/
public void setCacheLength(Integer cacheLength) {
this.cacheLength = cacheLength;
}//setCacheLength()//
/* (non-Javadoc)
* @see com.foundation.web.IContent#getIsDownloaded()
*/
public Boolean getIsDownloaded() {
return isDownloaded;
}//getIsDownloaded()//
/**
* Gets whether the content should be downloaded versus displayed in the browser.
* @param isDownloaded Whether the content should be downloaded, or null if the default behavior should occur based on the mime type.
*/
public void setIsDownloaded(Boolean isDownloaded) {
this.isDownloaded = isDownloaded;
}//setIsDownloaded()//
/* (non-Javadoc)
* @see com.foundation.web.IContent#getDownloadName()
*/
public String getDownloadName() {
return downloadName == null ? "file.html" : downloadName;
}//getDownloadName()//
/**
* Sets the download name used when downloading the content (versus displaying it in the browser).
* @param downloadName The custom download name, or if null the default name will be used.
*/
public void setDownloadName(String downloadName) {
this.downloadName = downloadName;
}//setDownloadName()//
}//HtmlContent//

View File

@@ -0,0 +1,33 @@
/*
* Copyright (c) 2008,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.web;
import com.common.util.ICollection;
import com.foundation.web.interfaces.IContent;
public abstract class HtmlView implements IHtmlView {
/**
* Tests the value to retrieve a boolean result.
* This method will first try to convert the value to a boolean value, otherwise it will check if it is a collection and use whether it has any values, otherwise it will simply test if the value is non-null.
* @param value The value to test.
* @param invert Whether to invert the results.
* @return Whether the value results in a true value.
*/
public boolean test(Object value, boolean invert) {
boolean result = (value instanceof Boolean ? ((Boolean) value).booleanValue() : value instanceof ICollection ? ((ICollection) value).getSize() > 0 : value != null);
return invert ? !result : result;
}//test()//
/**
* Generates the HTML content object.
* @return The content for the reponse.
*/
public IContent generateContent() {
return new HtmlContent(generateHtmlSource());
}//generateContent()//
}//HtmlView//

View File

@@ -0,0 +1,16 @@
/*
* Copyright (c) 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.web;
public interface IHtmlView {
/**
* Called by the web server to generate HTML source.
* @return The HTML to be sent to the web browser.
*/
public String generateHtmlSource();
}//IHtmlView//

View File

@@ -0,0 +1,45 @@
package com.foundation.web;
import com.foundation.web.interfaces.IRequest;
import com.foundation.web.interfaces.IResponse;
import com.foundation.web.interfaces.ISession;
import com.foundation.web.SessionData;
import com.foundation.web.SecureSessionData;
/**
* Allows the application to specify special handling for access to static resources.
*/
public interface IResourceRequestHandler {
/**
* Gets a resource from the file system. The implementor can validate that the user has the rights to access the resource and can ensure that there is an SSL connection if needed.
* <p>A simple implementation will simply call: <code>response.setContentResource(requestPath, request.getCacheDate());</code>, a more complex implementation will test for an SSL connection for secure resources, and will ensure the user is logged in and has rights to access the resource for rights oriented resources.</p>
* @param requestPath The path to the requested resource. This is the request's URI already mapped to an actual resource.
* @param request The request metadata.
* @param response The response metadata.
* @param sessionData The non-secure session data.
* @param secureSessionData The secure session data, or null if the request was made over an insecure connection or no secure session data exists.
* @param isSecure Whether the request was made over a secure connection and provided the correct secure id.
*/
public void processRequest(String requestPath, IRequest request, IResponse response, SessionData sessionData, SecureSessionData secureSessionData, boolean isSecure);
/**
* Calls a handler to access the requested resource. The implementor can validate that the user has the rights to access the resource and can ensure that there is an SSL connection if needed.
* <p>A simple implementation will simply call: <code>response.setContentResource(requestPath, request.getCacheDate());</code>, a more complex implementation will test for an SSL connection for secure resources, and will ensure the user is logged in and has rights to access the resource for rights oriented resources.</p>
* @param handler The handler which will process the request.
* @param requestPath The path to the requested resource. This is the path from the application's default package name to the handler class using slashes instead of dots as separators.
* @param request The request metadata.
* @param response The response metadata.
* @param sessionData The non-secure session data.
* @param secureSessionData The secure session data, or null if the request was made over an insecure connection or no secure session data exists.
* @param isSecure Whether the request was made over a secure connection and provided the correct secure id.
*/
public void processRequest(IWebRequestHandler handler, String requestPath, IRequest request, IResponse response, SessionData sessionData, SecureSessionData secureSessionData, boolean isSecure);
/**
* Gets the maximum size of message allowed by the application beyond the header (for upload only).
* @param request The request whose header has been read, but whose body has not yet been read.
* @param sessionData The sessionData for the session that the request exists within. This may be null only if the application does not assign a session, or a session for this connection has not yet been assigned.
* @param secureSessionData The secure session data for the session. This may be null if a session has not or will not be assigned, or if the connection to the client is not secure.
* @return The maximum message size in bytes, or -1 if the message may be infinately large. This should be restricted unless the user is trusted to prevent malicious users from hogging network and system resources.
*/
public int getMaxMessageSize(IRequest request, SessionData sessionData, SecureSessionData secureSessionData);
}//IResourceRequestHandler//

View File

@@ -0,0 +1,12 @@
package com.foundation.web;
import com.foundation.web.interfaces.IRequest;
public interface IRetainRequestBufferHandler {
/**
* Determines whether the web server, when processing an incoming request, should retain every byte of the request. This can be useful for debugging, but even more for forwarding certain types of messages to another process without having to recreate the message.
* @param request The request being made by the client. Only the header for the request has been read at this point - the body is missing. This can be used to determine when to retain the message.
* @return Whether to retain the exact bytes received by the web server for this request.
*/
public boolean retainWholeRequestBuffer(IRequest request);
}//IRetainRequestBufferHandler//

View File

@@ -0,0 +1,11 @@
package com.foundation.web;
/**
* An interface implemented by objects stored in the SessionData's application data mapping (as the value) when the object wants notification that the session has been released.
*/
public interface ISessionLifecycleAware {
/**
* Called when the session is released.
*/
public void release();
}//ISessionLifecycleAware//

View File

@@ -0,0 +1,28 @@
/*
* Copyright (c) 2008,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.web;
import com.foundation.web.interfaces.IRequest;
import com.foundation.web.interfaces.IResponse;
import com.foundation.web.SecureSessionData;
import com.foundation.web.SessionData;
/**
* The interface for all web request handlers.
* <p>Note: Use WebRequestController as an extension point. Provides access to some useful functions.</p>
*/
public interface IWebRequestHandler {
/**
* Provides the request handler an opportunity to process the request.
* @param request The request metadata.
* @param response The response metadata.
* @param sessionData The application specific data object associated with this user's session.
* @param secureSessionData The application specific data object associated with this user's session if the user made the request over a secure connection and used the correct secure session identifier, otherwise null.
*/
public void processRequest(IRequest request, IResponse response, SessionData sessionData, SecureSessionData secureSessionData);
}//IWebRequestHandler//

View File

@@ -0,0 +1,88 @@
/*
* Copyright (c) 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.web;
public interface IWebServerApplicationDefaults {
public static final String RESOURCE_NOT_FOUND =
"<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01//EN\" \"http://www.w3.org/TR/html4/strict.dtd\">" +
"<HTML><HEAD><TITLE>The page cannot be found.</TITLE>" +
"<META HTTP-EQUIV=\"Content-Type\" Content=\"text/html; charset=UTF-8\">" +
"<STYLE type=\"text/css\">" +
" BODY { font: 8pt/12pt verdana }" +
" H1 { font: 13pt/15pt verdana }" +
" H2 { font: 8pt/12pt verdana }" +
" A:link { color: red }" +
" A:visited { color: maroon }" +
"</STYLE>" +
"</HEAD><BODY><TABLE width=500 border=0 cellspacing=10><TR><TD>" +
"<h1>The page cannot be found.</h1>" +
"The page you are looking for might have been removed, had its name changed, or is temporarily unavailable." +
"<hr>" +
"<p>Please try the following:</p>" +
"<ul>" +
"<li>Make sure that the Web site address displayed in the address bar of your browser is spelled and formatted correctly.</li>" +
"<li>If you reached this page by clicking a link, contact the Web site administrator to alert them that the link is incorrectly formatted.</li>" +
"<li>Click the <a href=\"javascript:history.back(1)\">Back</a> button to try another link.</li>" +
"</ul>" +
"<h2>HTTP Error 404 - File or directory not found.</h2>" +
"</TD></TR></TABLE></BODY></HTML>";
public static final String INVALID_ACCESS =
"<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01//EN\" \"http://www.w3.org/TR/html4/strict.dtd\">" +
"<HTML><HEAD><TITLE>The page cannot be accessed.</TITLE>" +
"<META HTTP-EQUIV=\"Content-Type\" Content=\"text/html; charset=UTF-8\">" +
"<STYLE type=\"text/css\">" +
" BODY { font: 8pt/12pt verdana }" +
" H1 { font: 13pt/15pt verdana }" +
" H2 { font: 8pt/12pt verdana }" +
" A:link { color: red }" +
" A:visited { color: maroon }" +
"</STYLE>" +
"</HEAD><BODY><TABLE width=500 border=0 cellspacing=10><TR><TD>" +
"<h1>The page cannot be found.</h1>" +
"You do not have authorization to access the requested resource." +
"<hr>" +
"<p>Please try the following:</p>" +
"<ul>" +
"<li>Make sure that the Web site address displayed in the address bar of your browser is spelled and formatted correctly.</li>" +
"<li>If you reached this page by clicking a link, contact the Web site administrator to alert them that the link is incorrectly formatted.</li>" +
"<li>Click the <a href=\"javascript:history.back(1)\">Back</a> button to try another link.</li>" +
"</ul>" +
"<h2>HTTP Error 404 - Invalid access.</h2>" +
"</TD></TR></TABLE></BODY></HTML>";
public static final String TLS_FAILURE =
"<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01//EN\" \"http://www.w3.org/TR/html4/strict.dtd\">" +
"<HTML><HEAD><TITLE>Upgrade Your Browser!</TITLE>" +
"<META HTTP-EQUIV=\"Content-Type\" Content=\"text/html; charset=UTF-8\">" +
"<STYLE type=\"text/css\">" +
" BODY { font: 8pt/12pt verdana }" +
" H1 { font: 13pt/15pt verdana }" +
" H2 { font: 8pt/12pt verdana }" +
" A:link { color: red }" +
" A:visited { color: maroon }" +
"</STYLE>" +
"</HEAD><BODY><TABLE width=500 border=0 cellspacing=10><TR><TD>" +
"<h1>You are unable to access this website.</h1>" +
"Please upgrade your browser in order to access this website. Your browser does not support the latest SSL security standards and is unable to access this web content as a direct result. IE 6 is not supported at all. IE 7 & IE 8 are supported if you have the latest patches. All other browsers simply need to be updated to the latest version." +
"<hr>" +
"<h2>For Internet Explorer</h2>" +
"<p>Please try the following:</p>" +
"<ol>" +
"<li>Click the Help menu (tap the Alt key if you don't see any menus) and select \"About Internet Explorer\"</li>" +
"<li>If the version number is 6 you will need to either install Internet Explorer 7 or higher, or you may alternatively install Mozilla Firefox, Chrome, or Safari.</li>" +
"<li>If the version number is 7 or 8 you will need to run Windows Update from the Start menu (bottom left corner of the screen in most cases - then select All Programs for Vista or Windows 7). You can simply patch the version you have, or upgrade to a newer major version of IE.</li>" +
"</ol>" +
"<h2>For Mozilla Firefox</h2>" +
"<p>Go to the <a href='http://www.mozilla.org/firefox/'>Mozilla Firefox</a> website and click the \"Upgrade Your Firefox\" link and follow the instructions.</p>" +
"<p>If you are still having problems, click on the Tools menu and select \"Options...\", then select the \"Advanced\" button along the top, select the \"Encryption\" tab, and make sure that both \"Use SSL 3.0\" and \"Use TLS 1.0\" are checked.</p>" +
"<h2>For Google Chrome</h2>" +
"<p>Chrome auto updates in the background, but if you are still having problems you can reinstall it: Go to the <a href='http://chrome.google.com'>Google Chrome</a> website and click the \"Download Google Chrome\" link and follow the instructions.</p>" +
"<p>If you are still having problems, click on the wrench icon on the upper right of the window and select \"Options\", then select the \"Under the Hood\" button along the top, scroll to the bottom, and make sure that \"Use SSL 2.0\" is NOT checked.</p>" +
"<h2>For Apple Safari</h2>" +
"<p>Safari has a built in auto update system, but if you are still having problems you can reinstall it: Go to the <a href='http://www.apple.com/safari/'>Apple Safari</a> website and click the \"Download Now\" link and follow the instructions.</p>" +
"</TD></TR></TABLE></BODY></HTML>";
}//IWebServerApplicationDefaults//

View File

@@ -0,0 +1,21 @@
package com.foundation.web;
import com.foundation.web.interfaces.IRequest;
import com.foundation.web.interfaces.IResponse;
import com.foundation.web.interfaces.ISession;
/**
* Allows the application's default handling of webdav requests to be preempted.
*/
public interface IWebdavRequestHandler {
/**
* Processes the incoming web dav request.
* @param requestPath The path to the requested resource. This is the request's URI already mapped to an actual resource.
* @param request The request metadata.
* @param response The response metadata.
* @param sessionData The non-secure session data.
* @param secureSessionData The secure session data, or null if the request was made over an insecure connection or no secure session data exists.
* @param isSecure Whether the request was made over a secure connection and provided the correct secure id.
*/
public void processRequest(String requestPath, IRequest request, IResponse response, SessionData sessionData, SecureSessionData secureSessionData, boolean isSecure);
}//IWebdavRequestHandler//

View File

@@ -0,0 +1,32 @@
package com.foundation.web;
import java.nio.ByteBuffer;
import com.foundation.web.interfaces.IMimeType;
import com.foundation.web.interfaces.IMimeTypeProvider;
/**
* Used to wrapper javascript responses to the web browser.
*/
public class JavascriptContent extends HtmlContent {
/**
* JavascriptContent constructor.
* @param content
*/
public JavascriptContent(ByteBuffer content) {
super(content);
}//JavascriptContent()//
/**
* TextContent constructor.
* @param content
*/
public JavascriptContent(String content) {
super(content);
}//TextContent()//
/* (non-Javadoc)
* @see com.foundation.web.HtmlContent#getMimeType(com.foundation.web.interfaces.IMimeTypeProvider)
*/
public IMimeType getMimeType(IMimeTypeProvider provider) {
return provider.getMimeType("js");
}//getMimeType()//
}//JavascriptContent//

View File

@@ -0,0 +1,32 @@
package com.foundation.web;
import java.nio.ByteBuffer;
import com.foundation.web.interfaces.IMimeType;
import com.foundation.web.interfaces.IMimeTypeProvider;
/**
* Used to wrapper JSON content for a response to the web browser.
*/
public class JsonContent extends HtmlContent {
/**
* JsonContent constructor.
* @param content
*/
public JsonContent(ByteBuffer content) {
super(content);
}//JsonContent()//
/**
* JsonContent constructor.
* @param content
*/
public JsonContent(String content) {
super(content);
}//JsonContent()//
/* (non-Javadoc)
* @see com.foundation.web.HtmlContent#getMimeType(com.foundation.web.interfaces.IMimeTypeProvider)
*/
public IMimeType getMimeType(IMimeTypeProvider provider) {
return provider.getMimeType("json");
}//getMimeType()//
}//JsonContent//

View File

@@ -0,0 +1,64 @@
/*
* Copyright (c) 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.web;
import com.foundation.web.interfaces.IMimeType;
public class MimeType implements IMimeType {
/** The mime name. */
private String mimeName;
/** The default number of seconds a cache should retain the resource, or one of the CACHE_LENGTH_xxx identifiers. */
private int defaultCacheLength;
/** Whether the content should be downloaded versus displayed in the client's browser. */
private boolean isDownloaded;
/** Whether the data is normally compressable (there is normally a reasonable size difference when the media is compressed using a general compression algorithm such as gzip - eg: jpeg's are not because they are compressed by default). */
private boolean isCompressable;
/**
* MimeType constructor.
* @param mimeName
* @param defaultCacheLength
* @param isDownloaded
* @param isCompressable Whether the data is normally compressable (there is normally a reasonable size difference when the media is compressed using a general compression algorithm such as gzip - eg: jpeg's are not because they are compressed by default).
*/
public MimeType(String mimeName, int defaultCacheLength, boolean isDownloaded, boolean isCompressable) {
this.mimeName = mimeName;
this.defaultCacheLength = defaultCacheLength;
this.isDownloaded = isDownloaded;
}//MimeType()//
/**
* Gets the official mime type name.
* @return The mime name recongized by browsers.
*/
public String getMimeName() {
return mimeName;
}//getMimeName()//
/* (non-Javadoc)
* @see com.foundation.web.interfaces.IMimeType#getDefaultCacheLength()
*/
public int getDefaultCacheLength() {
return defaultCacheLength;
}//getDefaultCacheLength()//
/* (non-Javadoc)
* @see com.foundation.web.interfaces.IMimeType#isDownloaded()
*/
public boolean isDownloaded() {
return isDownloaded;
}//isDownloaded()//
/* (non-Javadoc)
* @see com.foundation.web.interfaces.IMimeType#isCompressable()
*/
public boolean isCompressable() {
return isCompressable;
}//isCompressable()//
/* (non-Javadoc)
* @see java.lang.Object#toString()
*/
public String toString() {
return getMimeName();
}//toString()//
}//MimeType//

View File

@@ -0,0 +1,297 @@
/*
* Copyright (c) 2008,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.web;
import java.io.File;
import com.common.util.LiteHashMap;
import com.foundation.web.interfaces.IMimeType;
import com.foundation.web.interfaces.IMimeTypeProvider;
public class MimeTypeProvider implements IMimeTypeProvider {
/** Used when setting up a mime type to specify that the content of this type requires caching mechanisms to cache for up to 30 minutes. */
public static final int CACHE_LENGTH_DEFAULT = MimeType.CACHE_LENGTH_DEFAULT;
/** Used when setting up a mime type to specify that the content of this type requires caching mechanisms to never store the content in their cache. */
public static final int CACHE_LENGTH_NEVER_CACHE = MimeType.CACHE_LENGTH_NEVER_CACHE;
/** Used when setting up a mime type to specify that the content of this type requires caching mechanisms to always check for freshness with the server before using he cache. */
public static final int CACHE_LENGTH_ALWAYS_TEST = MimeType.CACHE_LENGTH_ALWAYS_TEST;
/** The mapping of mime type instances indexed by the official mime name. */
private LiteHashMap mimeNameToMimeTypeMap = new LiteHashMap(100);
/** The mapping of mime type instances indexed by the file extensions they support. */
private LiteHashMap fileExtensionToMimeTypeMap = new LiteHashMap(20);
/** Whether the mapping is immutable. This will be flagged to true before allowing multi-threaded access. */
private volatile boolean isImmutable = false;
/**
* MimeTypeProvider constructor.
*/
public MimeTypeProvider() {
setMimeMap("abs", "audio/x-mpeg");
setMimeMap("ai", "application/postscript");
setMimeMap("aif", "audio/x-aiff");
setMimeMap("aifc", "audio/x-aiff");
setMimeMap("aiff", "audio/x-aiff");
setMimeMap("aim", "application/x-aim");
setMimeMap("art", "image/x-jg");
setMimeMap("asf", "video/x-ms-asf");
setMimeMap("asx", "video/x-ms-asf");
setMimeMap("au", "audio/basic");
setMimeMap("avi", "video/x-msvideo");
setMimeMap("avx", "video/x-rad-screenplay");
setMimeMap("bcpio", "application/x-bcpio");
setMimeMap("bin", "application/octet-stream", CACHE_LENGTH_NEVER_CACHE, true, true);
setMimeMap("bmp", "image/bmp", CACHE_LENGTH_DEFAULT);
setMimeMap("body", "text/html", false, true);
setMimeMap("cdf", "application/x-cdf");
setMimeMap("cer", "application/x-x509-ca-cert");
setMimeMap("class", "application/java");
setMimeMap("cpio", "application/x-cpio");
setMimeMap("csh", "application/x-csh");
setMimeMap("css", "text/css", false, true);
setMimeMap("dib", "image/bmp");
setMimeMap("doc", "application/msword", false, true);
setMimeMap("dtd", "application/xml-dtd", false, true);
setMimeMap("dv", "video/x-dv");
setMimeMap("dvi", "application/x-dvi");
setMimeMap("eps", "application/postscript");
setMimeMap("etx", "text/x-setext");
setMimeMap("exe", "application/octet-stream", CACHE_LENGTH_NEVER_CACHE, true, true);
setMimeMap("gif", "image/gif", CACHE_LENGTH_DEFAULT);
setMimeMap("gtar", "application/x-gtar", CACHE_LENGTH_NEVER_CACHE, true);
setMimeMap("gz", "application/x-gzip", CACHE_LENGTH_NEVER_CACHE, true);
setMimeMap("hdf", "application/x-hdf");
setMimeMap("hqx", "application/mac-binhex40");
setMimeMap("htc", "text/x-component");
setMimeMap("htm", "text/html", false, true);
setMimeMap("html", "text/html", false, true);
setMimeMap("hqx", "application/mac-binhex40");
setMimeMap("ief", "image/ief");
setMimeMap("jad", "text/vnd.sun.j2me.app-descriptor");
setMimeMap("jar", "application/java-archive");
setMimeMap("java", "text/plain", false, true);
setMimeMap("jnlp", "application/x-java-jnlp-file");
setMimeMap("jpe", "image/jpeg", CACHE_LENGTH_DEFAULT);
setMimeMap("jpeg", "image/jpeg", CACHE_LENGTH_DEFAULT);
setMimeMap("jpg", "image/jpeg", CACHE_LENGTH_DEFAULT);
setMimeMap("js", "text/javascript", false, true);
setMimeMap("jsf", "text/plain", false, true);
setMimeMap("json", "application/json", CACHE_LENGTH_NEVER_CACHE, false, true);
setMimeMap("jspf", "text/plain", false, true);
setMimeMap("kar", "audio/x-midi");
setMimeMap("latex", "application/x-latex", false, true);
setMimeMap("m3u", "audio/x-mpegurl");
setMimeMap("mac", "image/x-macpaint");
setMimeMap("man", "application/x-troff-man");
setMimeMap("mathml", "application/mathml+xml");
setMimeMap("me", "application/x-troff-me");
setMimeMap("mid", "audio/x-midi");
setMimeMap("midi", "audio/x-midi");
setMimeMap("mif", "application/x-mif");
setMimeMap("mov", "video/quicktime");
setMimeMap("movie", "video/x-sgi-movie");
setMimeMap("mp1", "audio/x-mpeg");
setMimeMap("mp2", "audio/x-mpeg");
setMimeMap("mp3", "audio/x-mpeg");
setMimeMap("mp4", "video/mp4");
setMimeMap("mpa", "audio/x-mpeg");
setMimeMap("mpe", "video/mpeg");
setMimeMap("mpeg", "video/mpeg");
setMimeMap("mpega", "audio/x-mpeg");
setMimeMap("mpg", "video/mpeg");
setMimeMap("mpv2", "video/mpeg2");
setMimeMap("ms", "application/x-wais-source");
setMimeMap("nc", "application/x-netcdf");
setMimeMap("oda", "application/oda");
setMimeMap("odb", "application/vnd.oasis.opendocument.database");
setMimeMap("odc", "application/vnd.oasis.opendocument.chart");
setMimeMap("odf", "application/vnd.oasis.opendocument.formula");
setMimeMap("odg", "application/vnd.oasis.opendocument.graphics");
setMimeMap("odi", "application/vnd.oasis.opendocument.image");
setMimeMap("odm", "application/vnd.oasis.opendocument.text-master");
setMimeMap("odp", "application/vnd.oasis.opendocument.presentation");
setMimeMap("ods", "application/vnd.oasis.opendocument.spreadsheet");
setMimeMap("odt", "application/vnd.oasis.opendocument.text");
setMimeMap("ogg", "application/ogg");
setMimeMap("otg ", "application/vnd.oasis.opendocument.graphics-template");
setMimeMap("oth", "application/vnd.oasis.opendocument.text-web");
setMimeMap("otp", "application/vnd.oasis.opendocument.presentation-template");
setMimeMap("ots", "application/vnd.oasis.opendocument.spreadsheet-template ");
setMimeMap("ott", "application/vnd.oasis.opendocument.text-template");
setMimeMap("pbm", "image/x-portable-bitmap", CACHE_LENGTH_DEFAULT);
setMimeMap("pct", "image/pict", CACHE_LENGTH_DEFAULT);
setMimeMap("pdf", "application/pdf", true, true);
setMimeMap("pgm", "image/x-portable-graymap");
setMimeMap("pic", "image/pict", CACHE_LENGTH_DEFAULT);
setMimeMap("pict", "image/pict", CACHE_LENGTH_DEFAULT);
setMimeMap("pls", "audio/x-scpls");
setMimeMap("png", "image/png", CACHE_LENGTH_DEFAULT);
setMimeMap("pnm", "image/x-portable-anymap");
setMimeMap("pnt", "image/x-macpaint");
setMimeMap("ppm", "image/x-portable-pixmap");
setMimeMap("ppt", "application/powerpoint");
setMimeMap("ps", "application/postscript");
setMimeMap("psd", "image/x-photoshop");
setMimeMap("qt", "video/quicktime");
setMimeMap("qti", "image/x-quicktime");
setMimeMap("qtif", "image/x-quicktime");
setMimeMap("ras", "image/x-cmu-raster");
setMimeMap("rdf", "application/rdf+xml");
setMimeMap("rgb", "image/x-rgb");
setMimeMap("rm", "application/vnd.rn-realmedia");
setMimeMap("roff", "application/x-troff");
setMimeMap("rtf", "application/rtf", false, true);
setMimeMap("rtx", "text/richtext", false, true);
setMimeMap("sh", "application/x-sh");
setMimeMap("shar", "application/x-shar");
setMimeMap("smf", "audio/x-midi");
setMimeMap("sit", "application/x-stuffit");
setMimeMap("snd", "audio/basic");
setMimeMap("src", "application/x-wais-source");
setMimeMap("sv4cpio", "application/x-sv4cpio");
setMimeMap("sv4crc", "application/x-sv4crc");
setMimeMap("swf", "application/x-shockwave-flash");
setMimeMap("t", "application/x-troff");
setMimeMap("tar", "application/x-tar", true);
setMimeMap("tcl", "application/x-tcl");
setMimeMap("tex", "application/x-tex");
setMimeMap("texi", "application/x-texinfo");
setMimeMap("texinfo", "application/x-texinfo");
setMimeMap("txt", "text/plain", false, true);
setMimeMap("text", "text/plain", false, true);
setMimeMap("tif", "image/tiff", CACHE_LENGTH_DEFAULT);
setMimeMap("tiff", "image/tiff", CACHE_LENGTH_DEFAULT);
setMimeMap("tr", "application/x-troff");
setMimeMap("tsv", "text/tab-separated-values");
setMimeMap("ulw", "audio/basic");
setMimeMap("ustar", "application/x-ustar");
setMimeMap("vxml", "application/voicexml+xml");
setMimeMap("xbm", "image/x-xbitmap");
setMimeMap("xht", "application/xhtml+xml", false, true);
setMimeMap("xhtml", "application/xhtml+xml", false, true);
setMimeMap("xml", "application/xml", false, true);
setMimeMap("xpm", "image/x-xpixmap");
setMimeMap("xsl", "application/xml", false, true);
setMimeMap("xslt", "application/xslt+xml", false, true);
setMimeMap("xul", "application/vnd.mozilla.xul+xml", false, true);
setMimeMap("xwd", "image/x-xwindowdump");
setMimeMap("wav", "audio/x-wav");
setMimeMap("svg", "image/svg+xml", false, true);
setMimeMap("svgz", "image/svg+xml", false, true);
setMimeMap("vsd", "application/x-visio");
setMimeMap("wbmp", "image/vnd.wap.wbmp");
setMimeMap("wml", "text/vnd.wap.wml");
setMimeMap("wmlc", "application/vnd.wap.wmlc");
setMimeMap("wmls", "text/vnd.wap.wmlscript");
setMimeMap("wmlscriptc", "application/vnd.wap.wmlscriptc");
setMimeMap("wmv", "video/x-ms-wmv");
setMimeMap("wrl", "x-world/x-vrml", false, true);
setMimeMap("Z", "application/x-compress");
setMimeMap("z", "application/x-compress");
setMimeMap("zip", "application/zip", true);
setMimeMap("xls", "application/vnd.ms-excel", false, true);
setMimeMap("doc", "application/vnd.ms-word", false, true);
setMimeMap("ppt", "application/vnd.ms-powerpoint", false, true);
}//MimeTypeProvider()//
/**
* Sets a mapping between an extension and a mime name.
* @param extension The file extension (without the preceeding dot).
* @param mimeName The mime name that describes the content type.
*/
public void setMimeMap(String extension, String mimeName) {
setMimeMap(extension, mimeName, CACHE_LENGTH_ALWAYS_TEST, false, false);
}//setMimeMap()//
/**
* Sets a mapping between an extension and a mime name.
* @param extension The file extension (without the preceeding dot).
* @param mimeName The mime name that describes the content type.
* @param isDownloaded Whether the mime type is something the user will download and save to file, versus opening in the browser.
*/
public void setMimeMap(String extension, String mimeName, boolean isDownloaded) {
setMimeMap(extension, mimeName, CACHE_LENGTH_ALWAYS_TEST, isDownloaded, false);
}//setMimeMap()//
/**
* Sets a mapping between an extension and a mime name.
* @param extension The file extension (without the preceeding dot).
* @param mimeName The mime name that describes the content type.
* @param isDownloaded Whether the mime type is something the user will download and save to file, versus opening in the browser.
* @param isCompressable Whether the data is normally compressable (there is normally a reasonable size difference when the media is compressed using a general compression algorithm such as gzip - eg: jpeg's are not because they are compressed by default).
*/
public void setMimeMap(String extension, String mimeName, boolean isDownloaded, boolean isCompressable) {
setMimeMap(extension, mimeName, CACHE_LENGTH_ALWAYS_TEST, isDownloaded, isCompressable);
}//setMimeMap()//
/**
* Sets a mapping between an extension and a mime name.
* @param extension The file extension (without the preceeding dot).
* @param mimeName The mime name that describes the content type.
* @param defaultCacheLength The default length of time to cache content of this mime type.
* @param isDownloaded Whether the mime type is something the user will download and save to file, versus opening in the browser.
*/
public void setMimeMap(String extension, String mimeName, int defaultCacheLength) {
setMimeMap(extension, mimeName, defaultCacheLength, false, false);
}//setMimeMap()//
/**
* Sets a mapping between an extension and a mime name.
* @param extension The file extension (without the preceeding dot).
* @param mimeName The mime name that describes the content type.
* @param defaultCacheLength The default length of time to cache content of this mime type.
* @param isDownloaded Whether the mime type is something the user will download and save to file, versus opening in the browser.
*/
public synchronized void setMimeMap(String extension, String mimeName, int defaultCacheLength, boolean isDownloaded) {
setMimeMap(extension, mimeName, defaultCacheLength, isDownloaded, false);
}//setMimeMap()//
/**
* Sets a mapping between an extension and a mime name.
* @param extension The file extension (without the preceeding dot).
* @param mimeName The mime name that describes the content type.
* @param defaultCacheLength The default length of time to cache content of this mime type.
* @param isDownloaded Whether the mime type is something the user will download and save to file, versus opening in the browser.
* @param isCompressable Whether the data is normally compressable (there is normally a reasonable size difference when the media is compressed using a general compression algorithm such as gzip - eg: jpeg's are not because they are compressed by default).
*/
public synchronized void setMimeMap(String extension, String mimeName, int defaultCacheLength, boolean isDownloaded, boolean isCompressable) {
if(!isImmutable) {
MimeType type = (MimeType) mimeNameToMimeTypeMap.get(mimeName);
if(type == null) {
type = new MimeType(mimeName, defaultCacheLength, isDownloaded, isCompressable);
mimeNameToMimeTypeMap.put(mimeName, type);
}//if//
fileExtensionToMimeTypeMap.put(extension.toLowerCase(), type);
}//if//
else {
throw new RuntimeException("Cannot modify the mime map while the WebServer is active.");
}//else//
}//setMimeMap()//
/**
* Makes the provider unchangeable to allow for multi-threaded access.
*/
public synchronized void setImmutable() {
isImmutable = true;
}//setImmutable()//
/* (non-Javadoc)
* @see com.foundation.web.IMimeTypeProvider#getMimeType(java.io.File)
*/
public IMimeType getMimeType(File file) {
int index = file.getName().lastIndexOf('.');
return getMimeType(index != -1 ? file.getName().substring(index + 1) : null);
}//getMimeType()//
/* (non-Javadoc)
* @see com.foundation.web.IMimeTypeProvider#getMimeType(java.lang.String)
*/
public IMimeType getMimeType(String extension) {
MimeType result = (MimeType) fileExtensionToMimeTypeMap.get(extension != null ? extension.toLowerCase() : null);
//Default the mime type to an octet stream so that everything is automatically supported.//
if(result == null) {
result = (MimeType) mimeNameToMimeTypeMap.get("application/octet-stream");
}//if//
return result;
}//getMimeType()//
}//MimeTypeProvider//

View File

@@ -0,0 +1,276 @@
package com.foundation.web;
import java.io.File;
import javax.net.ssl.SSLContext;
import com.foundation.web.WebApplication.ISslMapping;
import com.foundation.web.WebOptions.SslMapping;
import com.foundation.web.interfaces.ICachedResource;
import com.foundation.web.interfaces.IContent;
import com.foundation.web.interfaces.IMimeTypeProvider;
import com.foundation.web.interfaces.IPassThroughDomain;
import com.foundation.web.interfaces.IRequest;
import com.foundation.web.interfaces.IResponse;
import com.foundation.web.interfaces.ISession;
import com.foundation.web.interfaces.IWebApplication;
import com.foundation.web.interfaces.IWebApplicationContainer;
/**
* Allows a domain to be mapped to another service in another process. Currently only useable with SSL.
* A socket will be created to the given address:port when an SSL socket is received with the given domain in the TLS handshake. All data will be sent directly to the receiving process via this extra socket, and the extra socket will be listened to for data that needs to be encrypted and forwarded to the client.
*/
public class PassThroughDomain implements IPassThroughDomain {
/** The name used to map the web application to its self when reloading a webapp bundle. */
private String name = null;
/** The container for this application. This will be set once the application is added to the server and should no longer be modified. */
private IWebApplicationContainer webApplicationContainer = null;
/** The domains that this web server accepts. */
private String[] domains = null;
/** The services to listen for connections on. Should only ever include "SSL" currently. */
private String[] services = null;
/** The address that all content will be passed through to. */
private String address = null;
/** The port that all content will be passed through to. */
private int port = 0;
/** The mappings between domains and SSLContext's. */
private ISslMapping[] sslMappings = null;
/**
* PassThroughDomain constructor.
* @param name The name of the web application (unique), used by the web server to link resources back to the web application when it is restarted. This should never change between runnings of the web app.
* @param services Currently only "SSL" is accepted.
* @param domains The domains for which this web application is used.
* @param address The address of the remote process receiving the content sent by the client. Can be "localhost".
* @param port The port of the remote process.
* @param sslMappings The ssl mappings to be used.
*/
public PassThroughDomain(String name, String[] services, String[] domains, String address, int port, WebOptions.SslMapping[] sslMappings) {
this.name = name;
this.domains = domains;
this.address = address;
this.port = port;
this.services = services;
this.sslMappings = (ISslMapping[]) sslMappings.clone();
}//PassThroughDomain()//
/* (non-Javadoc)
* @see com.foundation.web.interfaces.IPassThroughDomain#getAddress()
*/
public String getAddress() {
return address;
}//getAddress()//
/* (non-Javadoc)
* @see com.foundation.web.interfaces.IPassThroughDomain#getPort()
*/
public int getPort() {
return port;
}//getPort()//
/* (non-Javadoc)
* @see com.foundation.web.interfaces.IWebApplication#getName()
*/
public String getName() {
return name;
}//getName()//
/* (non-Javadoc)
* @see com.foundation.web.interfaces.IWebApplication#getCompressResponses()
*/
public boolean getCompressResponses() {
return false;
}//getCompressResponses()//
/* (non-Javadoc)
* @see com.foundation.web.interfaces.IWebApplication#getDefaultPackageName()
*/
public String getDefaultPackageName() {
return null;
}//getDefaultPackageName()//
/* (non-Javadoc)
* @see com.foundation.web.interfaces.IWebApplication#setCacheCuttoffSize(long)
*/
public void setCacheCuttoffSize(long cacheCutoffSize) {
}//setCacheCuttoffSize()//
/* (non-Javadoc)
* @see com.foundation.web.interfaces.IWebApplication#clearCaches()
*/
public void clearCaches() {
}//clearCaches()//
/* (non-Javadoc)
* @see com.foundation.web.interfaces.IWebApplication#release()
*/
public void release() {
}//release()//
/* (non-Javadoc)
* @see com.foundation.web.interfaces.IWebApplication#serializeSessions()
*/
public File serializeSessions() {
return null;
}//serializeSessions()//
/* (non-Javadoc)
* @see com.foundation.web.interfaces.IWebApplication#deserializeSessions(java.io.File)
*/
public boolean deserializeSessions(File sessionFile) {
return true;
}//deserializeSessions()//
/* (non-Javadoc)
* @see com.foundation.web.interfaces.IWebApplication#getWebApplicationContainer()
*/
public IWebApplicationContainer getWebApplicationContainer() {
return webApplicationContainer;
}//getWebApplicationContainer()//
/* (non-Javadoc)
* @see com.foundation.web.interfaces.IWebApplication#completeInitialization(com.foundation.web.interfaces.IWebApplicationContainer)
*/
public void completeInitialization(IWebApplicationContainer webApplicationContainer) {
if(webApplicationContainer == null) {
throw new IllegalArgumentException("Must provide a web server reference.");
}//if//
else if(this.webApplicationContainer != null) {
throw new IllegalArgumentException("Cannot complete the initialization of a web application twice.");
}//else if//
this.webApplicationContainer = webApplicationContainer;
}//completeInitialization()//
/* (non-Javadoc)
* @see com.foundation.web.server.IWebApplication#getCachedResource(java.lang.String)
*/
public ICachedResource getCachedResource(String canonicalPath) {
return null;
}//getCachedResource()//
/* (non-Javadoc)
* @see com.foundation.web.server.IWebApplication#getMaxCachedResourceSize()
*/
public long getMaxCachedResourceSize() {
return 0;
}//getMaxCachedResourceSize()//
/* (non-Javadoc)
* @see com.foundation.web.server.IWebApplication#getMimeTypeProvider()
*/
public IMimeTypeProvider getMimeTypeProvider() {
return null;
}//getMimeTypeProvider()//
/* (non-Javadoc)
* @see com.foundation.web.server.IWebApplication#isCachingResources()
*/
public boolean isCachingResources() {
return false;
}//isCachingResources()//
/* (non-Javadoc)
* @see com.foundation.web.server.IWebApplication#setCachedResource(java.lang.String, java.io.File, byte[])
*/
public void setCachedResource(String canonicalPath, File file, byte[] contents) {
}//setCachedResource()//
/* (non-Javadoc)
* @see com.foundation.web.interfaces.IWebApplication#createSecureSession(com.foundation.web.interfaces.ISession)
*/
public void createSecureSession(ISession session) {
}//createSecureSession()//
/* (non-Javadoc)
* @see com.foundation.web.interfaces.IWebApplication#createSession()
*/
public ISession createSession() {
return null;
}//createSession()//
/* (non-Javadoc)
* @see com.foundation.web.interfaces.IWebApplication#recreateSession(java.lang.String)
*/
public ISession recreateSession(String sessionId) {
return null;
}//recreateSession()//
/* (non-Javadoc)
* @see com.foundation.web.interfaces.IWebApplication#getSessionData(com.foundation.web.interfaces.ISession)
*/
public byte[] getSessionData(ISession session) {
return null;
}//getSessionData()//
/* (non-Javadoc)
* @see com.foundation.web.interfaces.IWebApplication#getMaxMessageSize(com.foundation.web.interfaces.IRequest, java.lang.Object, java.lang.Object)
*/
public int getMaxMessageSize(IRequest request, Object sessionData, Object secureSessionData) {
return 0;
}//getMaxMessageSize()//
/* (non-Javadoc)
* @see com.foundation.web.server.IWebServerApplication#getBaseDirectory()
*/
public File getBaseDirectory() {
return null;
}//getBaseDirectory()//
/* (non-Javadoc)
* @see com.foundation.web.interfaces.IWebApplication#getExternalBaseDirectory()
*/
public File getExternalBaseDirectory() {
return null;
}//getExternalBaseDirectory()//
/* (non-Javadoc)
* @see com.foundation.web.interfaces.IWebApplication#getCacheBaseDirectory()
*/
public File getCacheBaseDirectory() {
return null;
}//getCacheBaseDirectory()//
/* (non-Javadoc)
* @see com.foundation.web.interfaces.IWebApplication#initiateOpenIdAuth(java.lang.String)
*/
public String initiateOpenIdAuth(String userId) {
return null;
}//initiateOpenIdAuth()//
/* (non-Javadoc)
* @see com.foundation.web.server.IWebServerApplication#getDomains()
*/
public String[] getDomains() {
return domains;
}//getDomains()//
/* (non-Javadoc)
* @see com.foundation.web.server.IWebApplication#getServices()
*/
public String[] getServices() {
return services;
}//getServices()//
/* (non-Javadoc)
* @see com.foundation.web.server.IWebApplication#getSslContext(java.lang.String)
*/
public SSLContext getSslContext(String domain) {
SSLContext result = null;
//Search for the first matching domain. Note that the domain passed may contain sub-domain markings while the ssl mapping may be just the top level domain.//
for(int index = 0, size = sslMappings == null ? 0 : sslMappings.length; result == null && index < size; index++) {
String[] domains = sslMappings[index].getDomains();
for(int domainIndex = 0; domainIndex < domains.length; domainIndex++) {
//TODO: Support wild cards here!
if(domain.equals(domains[domainIndex])) {
result = sslMappings[index].getSslContext();
}//if//
}//for//
}//for//
return result;
}//getSslContext()//
/* (non-Javadoc)
* @see com.foundation.web.server.IWebServerApplication#getErrorContent(int)
*/
public IContent getErrorContent(int errorCode) {
return null;
}//getErrorContent()//
/* (non-Javadoc)
* @see com.foundation.web.server.IWebServerApplication#getErrorHeader(int)
*/
public String getErrorHeader(int errorCode) {
return null;
}//getErrorHeader()//
/* (non-Javadoc)
* @see com.foundation.web.server.IWebServerApplication#getSession(java.lang.String)
*/
public ISession getSession(String sessionId) {
return null;
}//getSession()//
/* (non-Javadoc)
* @see com.foundation.web.interfaces.IWebApplication#processRequest(com.foundation.web.interfaces.IRequest, com.foundation.web.interfaces.IResponse, com.foundation.web.interfaces.ISession, boolean, boolean)
*/
public void processRequest(IRequest request, IResponse response, ISession session, boolean isSecure, boolean clientHadBadSession) {
response.setForwardUri("https://" + request.getHost() + request.getUri() + (request.getParameterString() != null ? request.getParameterString() : ""));
}//processRequest()//
/* (non-Javadoc)
* @see com.foundation.web.interfaces.IWebApplication#retainWholeRequestBuffer(com.foundation.web.interfaces.IRequest)
*/
public boolean retainWholeRequestBuffer(IRequest request) {
return false;
}//retainWholeRequestBuffer()//
}//PassThroughDomain//

View File

@@ -0,0 +1,91 @@
package com.foundation.web;
import java.io.File;
import java.security.InvalidParameterException;
import java.util.Date;
import com.common.debug.Debug;
import com.common.io.StreamSupport;
import com.foundation.web.interfaces.*;
/**
* Some support method for the response.
*/
public class ResponseSupport {
/**
* Sets the content using a resource contained in the application's base directory.
* @param IResponse The IResponse whose content is being set.
* @param resource The resource to use as the content of the IResponse.
* @deprecated
*/
public static void setContentResource(IResponse response, String resource) {
setContentResource(response, resource, response.getRequest(), null, null, false);
}//setContentResource()//
/**
* Sets the content using a resource contained in the application's base directory.
* @param IResponse The IResponse whose content is being set.
* @param resource The resource to use as the content of the IResponse.
* @param clientCacheDate The date the client supplied for the IResponse it has cached. If non-null, this can be used to send a 304 Not Modified message back to the client if the resource is unchanged.
* @deprecated
*/
public static void setContentResource(IResponse response, String resource, Date clientCacheDate) {
setContentResource(response, resource, response.getRequest(), clientCacheDate, null, false);
}//setContentResource()//
/**
* @param resource The resource to use as the content of the IResponse.
* @param IResponse The IResponse whose content is being set.
* @param request The request whose refering URL is used to create a relative path for the resource. If null, then the resource will be relative to the web app's base directory.
* @deprecated
*/
public static void setContentResource(IResponse response, String resource, IRequest request) {
setContentResource(response, resource, request, null, null, false);
}//setContentResource()//
/**
* @param resource The resource to use as the content of the IResponse.
* @param IResponse The IResponse whose content is being set.
* @param request The request whose refering URL is used to create a relative path for the resource. If null, then the resource will be relative to the web app's base directory.
* @param isDownload Whether the resource should be downloaded rather than opened in the browser. If this is null then the default action will be taken based on the mime settings.
* @deprecated
*/
public static void setContentResource(IResponse response, String resource, IRequest request, Boolean isDownload) {
setContentResource(response, resource, request, null, isDownload, false);
}//setContentResource()//
/**
* Sets the content using a resource contained in the application's base directory.
* @param IResponse The IResponse whose content is being set.
* @param resource The resource to use as the content of the IResponse.
* @param request The request whose refering URL is used to create a relative path for the resource. If null, then the resource will be relative to the web app's base directory.
* @param clientCacheDate The date the client supplied for the IResponse it has cached. If non-null, this can be used to send a 304 Not Modified message back to the client if the resource is unchanged.
* @param isDownload Whether the resource should be downloaded rather than opened in the browser. If this is null then the default action will be taken based on the mime settings.
* @deprecated
*/
public static void setContentResource(IResponse response, String resource, IRequest request, Date clientCacheDate, Boolean isDownload) {
setContentResource(response, resource, request, clientCacheDate, isDownload, false);
}//setContentResource()//
/**
* Sets the content using a resource contained in the application's base directory.
* @param IResponse The IResponse whose content is being set.
* @param resource The resource to use as the content of the IResponse.
* @param request The request whose refering URL is used to create a relative path for the resource. If null, then the resource will be relative to the web app's base directory.
* @param clientCacheDate The date the client supplied for the IResponse it has cached. If non-null, this can be used to send a 304 Not Modified message back to the client if the resource is unchanged.
* @param isDownload Whether the resource should be downloaded rather than opened in the browser. If this is null then the default action will be taken based on the mime settings.
* @param infinateCache Whether the client should cache the resource indefinately. This is useful for overriding the default caching limits for things like images that will never change.
* @deprecated
*/
public static void setContentResource(IResponse response, String resource, IRequest request, Date clientCacheDate, Boolean isDownload, boolean infinateCache) {
FileContent.setContentResource(response, resource, request, clientCacheDate, isDownload, infinateCache);
}//setContentResource()//
/**
* Sets the content using a resource contained in the application's base directory.
* @param IResponse The IResponse whose content is being set.
* @param resource The resource to use as the content of the IResponse.
* @param request The request whose refering URL is used to create a relative path for the resource. If null, then the resource will be relative to the web app's base directory.
* @param clientCacheDate The date the client supplied for the IResponse it has cached. If non-null, this can be used to send a 304 Not Modified message back to the client if the resource is unchanged.
* @param isDownload Whether the resource should be downloaded rather than opened in the browser. If this is null then the default action will be taken based on the mime settings.
* @param infinateCache Whether the client should cache the resource indefinately. This is useful for overriding the default caching limits for things like images that will never change.
* @deprecated
*/
public static void setContentResource(IResponse response, File file, IRequest request, Date clientCacheDate, Boolean isDownload, boolean infinateCache) {
FileContent.setContentResource(response, file, request, clientCacheDate, isDownload, infinateCache);
}//setContentResource()//
}//ResponseSupport//

View File

@@ -0,0 +1,111 @@
package com.foundation.web;
import java.io.IOException;
import com.common.debug.Debug;
import com.common.io.IExternalizable;
import com.common.io.IObjectInputStream;
import com.common.io.IObjectOutputStream;
import com.common.util.ICollection;
import com.foundation.common.IEntity;
import com.foundation.controller.ModelInterface;
import com.foundation.controller.ReflectionResult;
import com.foundation.controller.ReflectionSuccess;
import com.foundation.event.IRequestHandler;
/**
* The base class for secure session data. Most applications should not need to extend this.
* <p><b>Warning: If you do extend this, it must have a default constructor, and may NOT be an anonymous or non-static inner class.</b></p>
*/
public class SecureSessionData implements IExternalizable {
/** The secure session data's context. */
private SessionData sessionData;
/** The user that is logged in, or null if no user is logged in. */
private volatile IEntity user;
/**
* SecureSessionData constructor.
*/
public SecureSessionData() {
}//SecureSessionData()//
/**
* Called after the session data has been setup to allow for application code to initialize.
*/
public void initialize() {
}//initialize()//
/**
* Called after the session has been terminated by the web server, to allow the application code to cleanup.
*/
public void release() {
}//release()//
/**
* Called by the WebSession to initialize this secure session data.
* @param sessionData The session data associated with the secure session.
*/
void initialize(SessionData sessionData) {
this.sessionData = sessionData;
initialize();
}//initialize()//
/**
* Gets the request handler which threads all requests for the session.
* @return The handler used to ensure all requests that access shared resources (reflections) are single threaded, and is used to create a reflection context for each view.
*/
public IRequestHandler getRequestHandler() {
return sessionData.getRequestHandler();
}//getRequestHandler()//
/**
* Gets the session data associated with the secure session.
* @return The session data.
*/
public SessionData getSessionData() {
return sessionData;
}//getSessionData()//
/**
* Gets the user instance for the logged in user.
* @return The user's modal.
*/
public IEntity getUser() {
return user;
}//getUser()//
/**
* Sets the user instance for the logged in user.
* @param user The user's modal.
*/
public void setUser(IEntity user) {
this.user = user;
markChanged();
}//setUser()//
/**
* Flags the session data as having changed in a significant way, requiring a repository update.
*/
public void markChanged() {
sessionData.markChanged();
}//markChanged()//
/* (non-Javadoc)
* @see com.common.io.IExternalizable#readExternal(com.common.io.IObjectInputStream)
*/
public Object readExternal(IObjectInputStream in) throws IOException, ClassNotFoundException {
Class userType = (Class) in.readObject();
Object userKey = in.readObject();
if(userKey != null) {
ReflectionResult result = ModelInterface.findEntities(userType, new Object[] {userKey});
if(result instanceof ReflectionSuccess) {
user = (IEntity) ((ICollection) ((ReflectionSuccess) result).getResult()).getFirst();
}//if//
else {
Debug.log(new RuntimeException("Failed to restore the logged in user."));
}//else//
}//if//
return null;
}//readExternal()//
/* (non-Javadoc)
* @see com.common.io.IExternalizable#writeExternal(com.common.io.IObjectOutputStream)
*/
public void writeExternal(IObjectOutputStream out) throws IOException {
//If the user has been set then write out the user's entity key.//
out.writeObject(user == null ? null : user.getClass());
out.writeObject(user == null ? null : user.entityGetKey());
}//writeExternal()//
}//SecureSessionData//

View File

@@ -0,0 +1,153 @@
package com.foundation.web;
import java.io.IOException;
import com.common.io.IExternalizable;
import com.common.io.IObjectInputStream;
import com.common.io.IObjectOutputStream;
import com.common.util.IIterator;
import com.common.util.IList;
import com.common.util.LiteHashMap;
import com.foundation.event.IRequestHandler;
/**
* Contains the basic information about a user web session.
*/
public class SessionData implements IExternalizable {
/** Whether the user is logged in. Actual login and user data must be stored in the secure session to ensure that all requests using it arrive via SSL. */
private boolean isLoggedIn = false;
/** The optional application specific data indexed by an application spectific key. Elements (values, not keys) which implement ISessionLifecycleAware will be released when the session is released. */
private LiteHashMap applicationDataMap;
/** The internal web session. */
private WebSession session;
/**
* SessionData constructor.
*/
public SessionData() {
}//SessionData()//
/**
* Called after the session data has been setup to allow for application code to initialize.
*/
public void initialize() {
}//initialize()//
/**
* Called after the session has been terminated by the web server or when a web application is reloading or being removed, to allow the application code to cleanup.
*/
public void release() {
if(applicationDataMap != null) {
for(IIterator iterator = applicationDataMap.valueIterator(); iterator.hasNext(); ) {
Object next = iterator.next();
if(next instanceof ISessionLifecycleAware) ((ISessionLifecycleAware) next).release();
}//for//
}//if//
}//release()//
/**
* Initializes the session data with the web session reference.
* @param session The private internal session.
*/
void initialize(WebSession session) {
this.session = session;
initialize();
}//initialize()//
/**
* Gets the request handler which threads all requests for the session.
* @return The handler used to ensure all requests that access shared resources (reflections) are single threaded, and is used to create a reflection context for each view.
*/
public IRequestHandler getRequestHandler() {
return session.getRequestHandler();
}//getRequestHandler()//
/**
* Determines whether the user is currently logged in.
* @return Whether the user is logged in according to the application.
*/
public boolean isLoggedIn() {
return isLoggedIn;
}//isLoggedIn()//
/**
* Determines whether the user is currently logged in.
* @return Whether the user is logged in according to the application.
*/
public void isLoggedIn(boolean isLoggedIn) {
this.isLoggedIn = isLoggedIn;
}//isLoggedIn()//
/**
* Flags the session data as having changed in a significant way, requiring a repository update.
*/
public void markChanged() {
session.markChanged();
}//markChanged()//
/**
* Gets the application data.
* @return The application specific data element.
*/
public Object getApplicationData(String key) {
return applicationDataMap == null ? null : applicationDataMap.get(key);
}//getApplicationData()//
/**
* Stores the application data in the session's application data map by the given key.
* The application data may implement ISessionLifecycleAware if it should be called when the session is being released. This will only be called for a normal closing of the session. The session may still be restored at some time in the future.
* The application data may implement IExternalizable if it wishes to persist when the session is serialized.
* @param key The key for the data. If the key is logically equal (equivalent) to an existing key, then the existing data will be replaced and returned.
* @param applicationData The application specific data element.
*/
public void setApplicationData(String key, Object applicationData) {
if(applicationDataMap == null) {
applicationDataMap = new LiteHashMap(10);
}//if//
applicationDataMap.put(key, applicationData);
}//setApplicationData()//
/* (non-Javadoc)
* @see com.common.io.IExternalizable#readExternal(com.common.io.IObjectInputStream)
*/
public Object readExternal(IObjectInputStream in) throws IOException, ClassNotFoundException {
int valueCount = 0;
isLoggedIn = in.readBoolean();
valueCount = in.readInt();
if(valueCount > 0) {
applicationDataMap = new LiteHashMap(valueCount > 7 ? (int) Math.ceil(valueCount * 1.2f) : 10);
for(int index = 0; index < valueCount; index++) {
String key = (String) in.readObject();
Object value = in.readObject();
applicationDataMap.put(key, value);
}//for//
}//if//
return null;
}//readExternal()//
/* (non-Javadoc)
* @see com.common.io.IExternalizable#writeExternal(com.common.io.IObjectOutputStream)
*/
public void writeExternal(IObjectOutputStream out) throws IOException {
String[] keys = applicationDataMap != null ? new String[applicationDataMap.getSize()] : null;
int valueCount = 0;
out.writeBoolean(isLoggedIn);
if(keys != null) {
applicationDataMap.getKeys(keys);
for(int index = 0; index < keys.length; index++) {
if(applicationDataMap.get(keys[index]) instanceof IExternalizable) valueCount++;
}//for//
}//if//
out.writeInt(valueCount);
if(keys != null) {
for(int index = 0; index < keys.length; index++) {
Object next = applicationDataMap.get(keys[index]);
if(next instanceof IExternalizable) {
out.writeObject(keys[index]);
out.writeObject(next);
}//if//
}//for//
}//if//
}//writeExternal()//
}//SessionData//

View File

@@ -0,0 +1,252 @@
package com.foundation.web;
import java.io.IOException;
import java.io.InputStream;
import java.nio.ByteBuffer;
import java.nio.channels.SocketChannel;
import java.util.Date;
import com.common.debug.Debug;
import com.foundation.web.interfaces.IContent;
import com.foundation.web.interfaces.IMimeType;
import com.foundation.web.interfaces.IMimeTypeProvider;
public class StreamedContent implements IContent {
private SocketChannel channel = null;
private Runnable releaseChannelHandler = null;
private ByteBuffer buffer = null;
private int size = 0;
private int chunkSize = 0;
/**
* StreamedContent constructor.
* Call this method if there is no content for a response.
*/
public StreamedContent() {
}//StreamedContent()//
/**
* StreamedContent constructor.
* @param channel The socket channel to load content from.
* @param buffer The optional buffer to load content from. The content will begin with the bytes remaining on this buffer before reading content from the socket channel.
* @param releaseChannelHandler The handler called when the content is released. Used to cleanup after the channel (place it back in the pool to be reused, or closed).
* @param size The size of the content if known. Don't call this constructor if the size is unknown (a chunked stream).
*/
public StreamedContent(SocketChannel channel, ByteBuffer buffer, Runnable releaseChannelHandler, int size) {
this.channel = channel;
this.buffer = buffer;
this.releaseChannelHandler = releaseChannelHandler;
this.size = size;
}//StreamedContent()//
/**
* StreamedContent constructor.
* @param channel The socket channel to load content from.
* @param buffer The optional buffer to load content from. The content will begin with the bytes remaining on this buffer before reading content from the socket channel.
* @param releaseChannelHandler The handler called when the content is released. Used to cleanup after the channel (place it back in the pool to be reused, or closed).
*/
public StreamedContent(SocketChannel channel, ByteBuffer buffer, Runnable releaseChannelHandler) throws IOException {
this.channel = channel;
this.buffer = buffer;
this.releaseChannelHandler = releaseChannelHandler;
this.size = -1;
channel.configureBlocking(false);
}//StreamedContent()//
public long getStart() {
return 0;
}//getStart()//
public long getEnd() {
return 0;
}//getEnd()//
public long getSize() {
return 0;
}//getSize()//
/**
* Avoid a BufferOverflowException due to Sun IDIOTs (can't say that strongly enough). Occurs when the source has more bytes available than the destination has space. Modify the limit to only copy the bytes the destination can handle.
* @param dst The destination byte buffer (copying to).
* @param src The source byte buffer (copying from).
* @return The number of bytes transfered.
*/
private int put(ByteBuffer dst, ByteBuffer src) {
int count = src.remaining();
if(dst.remaining() < this.buffer.remaining()) {
int limit = src.limit();
src.limit(src.position() + dst.remaining());
dst.put(src);
src.limit(limit);
}//if//
else dst.put(src);
return count - src.remaining();
}//put()//
/* (non-Javadoc)
* @see com.foundation.web.interfaces.IContent#get(java.nio.ByteBuffer)
*/
public int get(ByteBuffer buffer) {
int result = CONTENT_END;
try {
if(size != -1) {
if(size != 0) {
if(this.buffer != null && this.buffer.hasRemaining()) {
//Copy the data from our source buffer to the destination buffer.//
result = put(buffer, this.buffer);
//Track the count of remaining bytes to transfer.//
size -= result;
}//if//
else {
int count = channel.read(buffer);
if(count == -1) result = CONTENT_END;
else if(count == 0) result = CONTENT_PENDING;
else {
result = count;
size -= count;
}//else//
}//else//
}//if//
else {
result = CONTENT_END;
}//else//
}//if//
else {
//Report a finished message.//
if(chunkSize == -1) {
if(this.buffer.remaining() > 0) {
result = put(buffer, this.buffer);
}//if//
else {
result = CONTENT_END;
}//else//
}//if//
else {
//Lazily create a buffer if necessary.//
if(this.buffer == null) {
this.buffer = ByteBuffer.allocate(2000);
}//if//
//Read as many bytes from the channel as possible.//
this.buffer.compact();
channel.read(this.buffer);
this.buffer.flip();
//Check to see if the chunk ends within this buffer.//
if(this.buffer.remaining() > chunkSize) {
//If this is the beginning of a new chunk then read the header before copying to the outgoing buffer, otherwise put only the bytes in the current chunk into the outgoing buffer.//
if(chunkSize == 0) {
StringBuffer chunkHeader = new StringBuffer(100);
//Read the next chunk header.//
while(this.buffer.hasRemaining() && (chunkHeader.length() < 2 || !(chunkHeader.charAt(chunkHeader.length() - 2) == '\r' && chunkHeader.charAt(chunkHeader.length() - 1) == '\n'))) {
chunkHeader.append((char) this.buffer.get());
}//while//
//If we found the whole chunk header then read the next chunk size, otherwise we will have to try again when more bytes are available on the stream.//
if(chunkHeader.length() > 1 && chunkHeader.charAt(chunkHeader.length() - 2) == '\r' && chunkHeader.charAt(chunkHeader.length() - 1) == '\n') {
String header = chunkHeader.toString();
if(header.length() > 2) {
int semiIndex = header.indexOf(';');
//Read the chunk size as a hex number. The size is either ended by a semi-colon (if using extensions) or a \r\n.//
chunkSize = Integer.parseInt(header.substring(0, semiIndex == -1 ? header.length() - 2 : semiIndex), 16);
}//if//
//If we just have a \r\n then it marks the end of the message.//
if(header.length() == 2 || chunkSize == 0) {
//Mark the message as completed.//
chunkSize = -1;
//Place the last two characters in the outgoing buffer.//
this.buffer.rewind();
result = put(buffer, this.buffer);
}//if//
else {
//Increase the chunk size to include the header and the trailing \r\n.//
chunkSize += header.length() + 2;
//Place as many bytes as possible in the outgoing buffer.//
this.buffer.rewind();
//Handle a small enough chunk that we have more bytes in our buffer than just the one chunk.//
//Transfer the bytes from the read buffer to the outgoing buffer.//
if(this.buffer.remaining() > chunkSize) {
int limit = this.buffer.limit();
//Write only the remaining bytes in the current chunk to the outgoing buffer.//
this.buffer.limit(chunkSize);
result = put(buffer, this.buffer);
this.buffer.limit(limit);
//Update the chunk size so we know when we finish a chunk.//
chunkSize -= result;
}//if//
else {
//Write the available bytes to the outgoing buffer.//
result = put(buffer, this.buffer);
//Update the chunk size so we know when we finish a chunk.//
chunkSize -= result;
}//else//
}//else//
}//if//
else {
//Rewind the buffer so we can try reading the header again when more bytes are available.//
this.buffer.rewind();
}//else//
}//if//
else {
int limit = this.buffer.limit();
//Write only the remaining bytes in the current chunk.//
this.buffer.limit(chunkSize);
result = put(buffer, this.buffer);
this.buffer.limit(limit);
//Update the chunk size so we know when we finish a chunk.//
chunkSize -= result;
}//else//
}//if//
else {
//Write to the outgoing buffer.//
result = put(buffer, this.buffer);
//Update the chunk size so we know when we finish a chunk.//
chunkSize -= result;
}//else//
}//else//
}//else//
}//try//
catch(Throwable e) {
Debug.log(e);
}//catch//
return result;
}//get()//
public void reset() {
throw new RuntimeException("Cannot reset this content type.");
}//reset()//
public void release() {
if(releaseChannelHandler != null) {
releaseChannelHandler.run();
}//if//
channel = null;
}//release()//
public Date getLastModifiedDate() {
return null;
}//getLastModifiedDate()//
public IMimeType getMimeType(IMimeTypeProvider provider) {
return null;
}//getMimeType()//
public String getCacheDirective() {
return null;
}//getCacheDirective()//
public Date getExpiresDirective() {
return null;
}//getExpiresDirective()//
public Integer getCacheLength() {
return null;
}//getCacheLength()//
public Boolean getIsDownloaded() {
return null;
}//getIsDownloaded()//
public String getDownloadName() {
return null;
}//getDownloadName()//
public void setDownloadName(String downloadName) {
}//setDownloadName()//
}//StreamedContent//

View File

@@ -0,0 +1,496 @@
/*
* Copyright (c) 2008,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.web;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
import java.util.Date;
import java.io.*;
import com.common.debug.Debug;
import com.common.io.StreamSupport;
import com.foundation.web.interfaces.*;
/**
* Encapsulates a file to be returned to a client as a response to a web request.
* The static methods provide assistance in setting up the file content properly and attaching it to the response object.
*/
public class TarFilesContent implements IContent {
/** The file whose content is being accessed. */
private File[] files = null;
/** The file names as represented in the tar being generated. */
private String[] names = null;
/** The starting position for the download (inclusive). */
private long start = 0;
/** The ending position for the download (inclusive). */
private long end;
/** The channel used to retrieve content from the file. This may be null if the channel is closed, or if the contents of the file were provided as a byte array. */
private FileChannel channel = null;
/** The contents of the file if provided. This allows a caching system to be used while still providing mime information on the file. If null then the channel should be used. */
// private byte[] fileContents = null;
/** The offset into the file contents of the next byte to be retrieved. */
// private int fileContentsOffset = 0;
/** The directive indicating to the client whether the response should be cached, or null if the default behavior should be used (depends on the MimeType or if provided, the cacheLength attribute). */
private String cacheDirective = null;
/** The date indicating to the client when any cache should become invalid, or null if the default behavior should be used (no date). */
private Date expiresDirective = null;
/** The number of seconds the client is allowed to cache the content, null if the mime type's default cache controls should be used, or one of the CACHE_LENGTH_xxx identifiers indicating how caching should be handled. */
private Integer cacheLength = null;
/** Whether the content should be downloaded, or null if the default behavior should occur based on the mime type. */
// private Boolean isDownloaded = null;
/** The custom download name, or if null the file name will be used. */
private String downloadName = null;
/** The extension used to identify the mime type. If null, then the file reference will be used. */
private String extension = null;
/** The timestamp of the last modification to any of the files. */
private long lastModified = 0;
/** The total size of all of the files, plus 512 byte headers for each and two empty headers at the end. */
private long totalSize = 0;
/** The size of each of the files. */
private long[] fileSizes = null;
/** The current offset within the whole tar file. */
private long currentOffset = 0;
/** The offset from the beginning of the current file's header to the byte next to be sent. */
private long currentFileOffset = 0;
/** The index of the current file being sent, or files.length if the last file has been sent (all that remains is the empty headers). */
private int currentFileIndex = 0;
/** The header for the current file. Will be null if not yet created for the current file. */
private byte[] currentHeader = null;
/** The count of filler (zero) bytes to write out before writing the next file header or end of stream empty headers. */
private int currentFillerCount = 0;
/**
* TarFilesContent constructor.
* @param files The array of files whose contents will be streamed to the client as a single tar file.
* @param name The name of the downloaded tar file (should probably end with .tar).
* @param start The index into the tar file being downloaded, or null if starting at the beginning.
* @param end The last byte of the file to be sent, or null if all should be sent.
*/
public TarFilesContent(File[] files, String[] names, String name, Long start, Long end) throws FileNotFoundException, IOException {
this.files = files;
this.names = names;
this.downloadName = name;
this.totalSize = 0;
this.lastModified = 0;
this.fileSizes = new long[files.length];
if(name == null) {
throw new IllegalArgumentException();
}//if//
for(int index = 0; index < files.length; index++) {
long fileSize = files[index].length();
this.fileSizes[index] = fileSize;
this.totalSize += fileSize + 512 + (512 - (fileSize % 512));
this.lastModified = Math.max(files[index].lastModified(), this.lastModified);
}//for//
//Two empty headers at the end.//
this.totalSize += 1024;
//Calculate the current file index and offset.//
if(start != null) {
long offset = 0;
currentFileIndex = 0;
//Search for the first file to start writing the stream with.//
while(currentFileIndex != fileSizes.length && offset + fileSizes[currentFileIndex] + 512 + (512 - (fileSizes[currentFileIndex] % 512)) < start.longValue()) {
//Include the file size, the header size, and the padding to the nearest half kilobyte at the end of the file.//
offset += fileSizes[currentFileIndex] + 512 + (512 - (fileSizes[currentFileIndex] % 512));
//Move to the next file.//
currentFileIndex++;
}//while//
currentFileOffset = start.longValue() - offset;
currentOffset = start.longValue();
}//if//
else {
currentFileIndex = 0;
currentFileOffset = 0;
currentOffset = 0;
}//else//
//Create the file channel for the current output file.//
if(currentFileIndex != files.length) {
//Set the position if we aren't still writing the file's header.//
if(currentFileOffset > 512) {
//Just need to write the buffering bytes before starting the next file.//
if(currentFileOffset - 512 > fileSizes[currentFileIndex]) {
currentFillerCount = (int) (512 - (fileSizes[currentFileIndex] % 512));
}//if//
else {
this.channel = new FileInputStream(files[currentFileIndex]).getChannel();
this.channel.position(currentFileOffset - 512);
}//else//
}//if//
}//if//
if(start != null) {
this.start = start.longValue();
}//if//
this.end = end != null ? end.longValue() : totalSize - 1;
}//TarFilesContent()//
/* (non-Javadoc)
* @see com.foundation.web.interfaces.IContent#release()
*/
public void release() {
if(channel != null) {
try {
channel.close();
}//try//
catch(Throwable e) {}
channel = null;
}//if//
}//release()//
/* (non-Javadoc)
* @see com.foundation.web.interfaces.IContent#get(java.nio.ByteBuffer)
*/
public int get(ByteBuffer buffer) {
int result = 0;
boolean closeChannel = false;
try {
// long debugPosition = currentFileIndex == files.length ? currentFileOffset : currentFileOffset - 512;
// String debugFileName = currentFileIndex == files.length ? "[final tar headers]" : files[currentFileIndex].getName();
//Set the buffer's limit (where we will stop filling it at) based on either the capacity of the buffer or the number of bytes left to send.//
buffer.limit((int) Math.min(buffer.capacity(), end + 1 - currentOffset + buffer.position()));
//Keep filling the buffer until it is full or we have sent the last byte.//
while(buffer.remaining() > 0 && currentOffset != (end + 1)) {
if(buffer.limit() == 0) {
closeChannel = true;
}//if//
else {
if(currentFillerCount > 0) {
byte[] temp = new byte[Math.min(currentFillerCount, buffer.remaining())];
buffer.put(temp, 0, temp.length);
currentFillerCount -= temp.length;
currentOffset += temp.length;
result += temp.length;
}//if//
//If we are the very end of the tar file then write out the empty headers.//
if(currentFileIndex == files.length) {
long writeCount = Math.min(buffer.remaining(), 1024 - currentFileOffset);
if(currentHeader == null) {
currentHeader = new byte[1024];
}//if//
buffer.put(currentHeader, (int) currentFileOffset, (int) writeCount);
currentFileOffset += writeCount;
currentOffset += writeCount;
result += (int) writeCount;
}//if//
else {
//Write the header.//
if(currentFileOffset < 512) {
long writeCount = Math.min(buffer.remaining(), 512 - currentFileOffset);
if(currentHeader == null) {
currentHeader = createHeader(files[currentFileIndex], names[currentFileIndex] != null ? names[currentFileIndex] : files[currentFileIndex].getName());
}//if//
buffer.put(currentHeader, (int) currentFileOffset, (int) writeCount);
currentFileOffset += writeCount;
currentOffset += writeCount;
result += (int) writeCount;
}//if//
//Write the file contents.//
if(currentFileOffset >= 512) {
int writeCount = 0;
if(channel == null) {
channel = new FileInputStream(files[currentFileIndex]).getChannel();
}//if//
//Keep reading from the file until the buffer is full or the file is finished.//
while(!closeChannel && buffer.remaining() > 0) {
int nextWriteCount = channel.read(buffer);
if(nextWriteCount == -1) {
long partial;
closeChannel = true;
partial = ((currentFileOffset + writeCount) % 512);
//If the file ends on a 512 byte offset then don't fill at all, otherwise fill to the next offset.//
currentFillerCount = partial == 0 ? 0 : (int) (512 - partial);
}//if//
else {
writeCount += nextWriteCount;
}//else//
}//while//
result += writeCount;
currentFileOffset += writeCount;
currentOffset += writeCount;
}//if//
}//else//
}//else//
if(closeChannel) {
try {
channel.close();
}//try//
catch(Throwable e) {}
channel = null;
currentFileIndex++;
currentFileOffset = 0;
currentHeader = null;
closeChannel = false;
// Debug.log("File download complete: " + file.getPath());
}//if//
}//while//
// Debug.log("Sending " + result + " file bytes (expected: " + buffer.limit() + ") starting at position " + debugPosition + " of " + debugFileName);
}//try//
catch(IOException e) {
try {channel.close();} catch(Throwable e2) {}
channel = null;
Debug.log(e);
}//catch//
return result;
}//get()//
/**
* Creates the header for the given file.
* @param file The file whose header is to be created.
* @param fileName The name of the file as represented in the tar. Not to exceed 255 bytes and should only use ascii characters.
* @return The header bytes (always sized to 512 bytes).
*/
private byte[] createHeader(File file, String fileName) throws IOException {
byte[] header = new byte[512];
byte[] name = fileName.getBytes("ASCII");
byte[] octalLength = Long.toOctalString(file.length()).getBytes("ASCII");
byte[] timestamp = Long.toOctalString(file.lastModified() / 1000).getBytes("ASCII");
long checksum = 0;
byte[] checksumBytes;
//Verify the name's max length.//
if(name.length > 255) {
throw new RuntimeException("Name too long!");
}//if//
if(octalLength.length > 11) {
throw new RuntimeException("File size too big!");
}//if//
//Write the file name to the header.//
if(name.length > 100) {
System.arraycopy(name, 0, header, 345, name.length - 100);
System.arraycopy(name, name.length - 100, header, 0, 100);
}//if//
else {
System.arraycopy(name, 0, header, 0, name.length);
}//else//
//Write the header fields.//
header[100] = 0x30;
header[101] = 0x31;
header[102] = 0x30;
header[103] = 0x30;
header[104] = 0x36;
header[105] = 0x36;
header[106] = 0x36;
header[107] = 0x00;
header[108] = 0x30;
header[109] = 0x30;
header[110] = 0x30;
header[111] = 0x30;
header[112] = 0x30;
header[113] = 0x30;
header[114] = 0x30;
header[115] = 0x00;
header[116] = 0x30;
header[117] = 0x30;
header[118] = 0x30;
header[119] = 0x30;
header[120] = 0x30;
header[121] = 0x30;
header[122] = 0x30;
header[123] = 0x00;
//Write the length of the file in octal characters.//
for(int index = 0; index < (11 - octalLength.length); index++) {header[124 + index] = 0x30;}
System.arraycopy(octalLength, 0, header, 124 + (11 - octalLength.length), octalLength.length);
header[135] = 0x00;
//Write the modified timestamp.//
for(int index = 0; index < (11 - timestamp.length); index++) {header[136 + index] = 0x30;}
System.arraycopy(timestamp, 0, header, 136 + (timestamp.length > 11 ? 0 : (11 - timestamp.length)), timestamp.length > 11 ? 11 : timestamp.length);
header[147] = 0x00;
//Use ascii spaces for the checksum calculation, then change this to a six diget octal checksum followed by a null and an ascii space.//
header[148] = 0x32;
header[149] = 0x32;
header[150] = 0x32;
header[151] = 0x32;
header[152] = 0x32;
header[153] = 0x32;
header[154] = 0x32;
header[155] = 0x32;
//Type flag 0 == normal file.//
header[156] = 0x30;
//Ignore the next 100 bytes.//
//Write the USTAR header.//
header[257] = 0x75;
header[258] = 0x73;
header[259] = 0x74;
header[260] = 0x61;
header[261] = 0x72;
header[262] = 0x00; //maybe 0x20 or 0x00
header[263] = 0x30; //maybe 0x20 or 0x30
header[264] = 0x30; //maybe 0x00 or 0x30
//Skip the next 80 bytes.//
// System.arraycopy(owner, 0, header, 265, owner.length);
//File name prefix written already.//
//Calculate the checksum.//
for(int index = 0; index < header.length; index++) {
checksum += header[index];
}//for//
checksum -= 144; //I am off by 144, not sure why, but it must have something to do with the padding put in for the checksum's final resting place [148-155].//
checksumBytes = Long.toOctalString(checksum).getBytes("ASCII");
//Write a zero checksum initially as an easy method of zero padding the checksum. Follow the six octal digets with a null and space.//
header[148] = 0x30;
header[149] = 0x30;
header[150] = 0x30;
header[151] = 0x30;
header[152] = 0x30;
header[153] = 0x30;
header[154] = 0x00;
header[155] = 0x20;
//Copy the checksum into the header using zeros to pad the number.//
System.arraycopy(checksumBytes, 0, header, 148 + (6 - checksumBytes.length), checksumBytes.length);
return header;
}//createHeader()//
/* (non-Javadoc)
* @see com.foundation.web.interfaces.IContent#getSize()
*/
public long getSize() {
return totalSize - start;
}//getSize()//
/* (non-Javadoc)
* @see com.foundation.web.interfaces.IContent#getStart()
*/
public long getStart() {
return start;
}//getStart()//
/* (non-Javadoc)
* @see com.foundation.web.interfaces.IContent#getEnd()
*/
public long getEnd() {
return end;
}//getEnd()//
/* (non-Javadoc)
* @see com.foundation.web.interfaces.IContent#reset()
*/
public void reset() {
if(channel != null) {
try {
channel.close();
}//try//
catch(Throwable e) {}
}//if//
try {
currentFileIndex = 0;
currentFileOffset = 0;
currentOffset = 0;
channel = new FileInputStream(files[currentFileIndex]).getChannel();
}//try//
catch(Throwable e) {
Debug.log(e);
}//catch//
}//reset()//
/* (non-Javadoc)
* @see com.foundation.web.interfaces.IContent#getLastModifiedDate()
*/
public Date getLastModifiedDate() {
return new Date(lastModified);
}//getLastModifiedDate()//
/* (non-Javadoc)
* @see com.foundation.web.interfaces.IContent#getMimeType(com.foundation.web.interfaces.IMimeTypeProvider)
*/
public IMimeType getMimeType(IMimeTypeProvider provider) {
return extension == null ? provider.getMimeType("tar") : provider.getMimeType(extension);
}//getMimeType()//
/**
* Sets the file's extension. Useful if only the file's contents are provided.
* @param extension The file extension identifying the mime type. Should not include the separator.
*/
public void setExtension(String extension) {
this.extension = extension;
}//getMimeType()//
/* (non-Javadoc)
* @see com.foundation.web.interfaces.IContent#getCacheDirective()
*/
public String getCacheDirective() {
return cacheDirective;
}//getCacheDirective()//
/**
* Sets the custom cache directive to be used when sending the response to the client.
* @param cacheDirective The directive indicating to the client whether the response should be cached, or null if the default behavior should be used (depends on the MimeType or if provided, the cacheLength attribute).
*/
public void setCacheDirective(String cacheDirective) {
this.cacheDirective = cacheDirective;
}//getCacheDirective()//
/* (non-Javadoc)
* @see com.foundation.web.interfaces.IContent#getExpiresDirective()
*/
public Date getExpiresDirective() {
return expiresDirective;
}//getExpiresDirective()//
/**
* Sets the default expires date directive to be used when sending the response to the client.
* @param expiresDirective The date indicating to the client when any cache should become invalid, or null if the default behavior should be used (no date).
*/
public void setExpiresDirective(Date expiresDirective) {
this.expiresDirective = expiresDirective;
}//setExpiresDirective()//
/* (non-Javadoc)
* @see com.foundation.web.interfaces.IContent#getCacheLength()
*/
public Integer getCacheLength() {
return cacheLength;
}//getCacheLength()//
/**
* Sets the cache length in terms of seconds, or one of the CACHE_LENGTH_xxx identifiers defined by IContent.
* <p>Note: This is an easier way of controlling caching than a custom CacheDirective. The custom cache directive will override this if provided.</p>
* @param cacheLength The number of seconds the client is allowed to cache the content, null if the mime type's default cache controls should be used, or one of the CACHE_LENGTH_xxx identifiers indicating how caching should be handled.
*/
public void setCacheLength(Integer cacheLength) {
this.cacheLength = cacheLength;
}//setCacheLength()//
/* (non-Javadoc)
* @see com.foundation.web.interfaces.IContent#getIsDownloaded()
*/
public Boolean getIsDownloaded() {
return Boolean.TRUE;
}//getIsDownloaded()//
/* (non-Javadoc)
* @see com.foundation.web.interfaces.IContent#getDownloadName()
*/
public String getDownloadName() {
return downloadName;
}//getDownloadName()//
/* (non-Javadoc)
* @see com.foundation.web.interfaces.IContent#setDownloadName(java.lang.String)
*/
public void setDownloadName(String downloadName) {
this.downloadName = downloadName;
}//setDownloadName()//
}//FileContent//

View File

@@ -0,0 +1,32 @@
package com.foundation.web;
import java.nio.ByteBuffer;
import com.foundation.web.interfaces.IMimeType;
import com.foundation.web.interfaces.IMimeTypeProvider;
/**
* Used to wrapper plain text responses to the web browser.
*/
public class TextContent extends HtmlContent {
/**
* TextContent constructor.
* @param content
*/
public TextContent(ByteBuffer content) {
super(content);
}//TextContent()//
/**
* TextContent constructor.
* @param content
*/
public TextContent(String content) {
super(content);
}//TextContent()//
/* (non-Javadoc)
* @see com.foundation.web.HtmlContent#getMimeType(com.foundation.web.interfaces.IMimeTypeProvider)
*/
public IMimeType getMimeType(IMimeTypeProvider provider) {
return provider.getMimeType("text");
}//getMimeType()//
}//TextContent//

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,323 @@
package com.foundation.web;
import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.security.KeyManagementException;
import java.security.KeyStore;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.NoSuchProviderException;
import java.security.UnrecoverableKeyException;
import java.security.cert.CertificateException;
import javax.net.ssl.KeyManagerFactory;
import javax.net.ssl.SSLContext;
import com.common.io.StreamSupport;
import com.common.util.IHashMap;
import com.common.util.LiteHashMap;
import com.foundation.web.WebApplication.SessionHandler;
import com.foundation.web.WebApplication.PathSubstitution;
import com.foundation.web.interfaces.IBadSessionHandler;
public class WebOptions {
private SessionHandler sessionHandler = null;
private String[] domains = null;
private String[] services = null;
private SslMapping[] sslMappings = null;
/** The directory where public static resources can be found - images, html, etc. */
private File baseDirectory = null;
/** An optional directory where files are externally stored. These files are not part of the webapp's general distribution. */
private File externalBaseDirectory = null;
/** An optional directory where cache files are stored. These files are not part of the webapp's general distribution and are fully re-creatable. */
private File cacheBaseDirectory = null;
private IHashMap nameSubstitutionMap = new LiteHashMap(10);
private IHashMap pathSubstitutionMap = new LiteHashMap(10);
private String defaultPackageName = null;
private long sessionTimeout = 3600000;
private long sessionCheckInterval = 3600000;
private IBadSessionHandler badSessionHandler = null;
/** Replaces the default handling of a user's access to static (file system based) resources. Allows for permission validation. */
private IResourceRequestHandler resourceRequestHandler = null;
/** Replaces the default handling of web dav requests. */
private IWebdavRequestHandler webdavRequestHandler = null;
/** Sets the cutoff size for caching a file in memory. This may be zero to stop caching resources alltogether. Defaults to 10KB. */
private long cacheCutoffSize = 10000;
/** Whether responses should be compressed by default when possible. Certain resources that compress poorly will of course ignore this directive. */
private boolean compressResponses = false;
/** The handler called by the web server when deciding whether to retain a request's full byte buffer (exactly what bytes make up the request as received). A null value will turn this feature off entirely. */
private IRetainRequestBufferHandler retainRequestBufferHandler = null;
/**
* Maps an SSL Context to a domain.
*/
public static class SslMapping implements WebApplication.ISslMapping {
private String[] domains;
private SSLContext sslContext;
private SslMapping(String[] domains, SSLContext sslContext) {
this.domains = domains;
this.sslContext = sslContext;
}//SslMapping()//
public String[] getDomains() {
return domains;
}//getDomains()//
public SSLContext getSslContext() {
return sslContext;
}//getSslContext()//
}//SslMapping//
/**
* Creates an SSL Mapping.
* @param domains The domain(s) the mapping applies to. This is not case sensitive. These must be exact matches (wild cards not yet supported).
* @param keyStore The path to the keystore for the SSL.
* @param keyStorePassword The password for the keystore.
* @param keyPassword The key's password.
* @return The ssl mapping that can be passed to the web application's constructor.
*/
public static SslMapping createSslMapping(String[] domains, File keyStore, String keyStorePassword, String keyPassword) throws KeyStoreException, NoSuchAlgorithmException, NoSuchProviderException, IOException, CertificateException, UnrecoverableKeyException, KeyManagementException {
//FileInputStream fin = null;
try {
KeyStore keystore = KeyStore.getInstance("JKS");
KeyManagerFactory keyManagerFactory;
SSLContext sslContext;
ByteArrayInputStream bin = new ByteArrayInputStream(StreamSupport.readBytes(keyStore));
//fin = new FileInputStream(keyStore);
keystore.load(bin, keyStorePassword.toCharArray());
keyManagerFactory = KeyManagerFactory.getInstance("SunX509", "SunJSSE");
keyManagerFactory.init(keystore, keyPassword.toCharArray());
sslContext = SSLContext.getInstance("TLSv1", "SunJSSE"); //SSLv3
sslContext.init(keyManagerFactory.getKeyManagers(), null, null);
for(int index = 0; index < domains.length; index++) {
domains[index] = domains[index].toLowerCase();
}//for//
return new SslMapping((String[]) domains.clone(), sslContext);
}//try//
finally {
//if(fin != null) try {fin.close();} catch(Throwable e) {}
}//finally//
}//createSslMapping()//
/**
* WebOptions constructor.
*/
public WebOptions() {
}//WebOptions()//
public SessionHandler getSessionHandler() {
return sessionHandler;
}//getSessionHandler()//
/**
* Sets the session handler providing the user of the web application an easy way to add session functionality.
* @param sessionHandler The session application code.
*/
public void setSessionHandler(SessionHandler sessionHandler) {
this.sessionHandler = sessionHandler;
}//setSessionHandler()//
public String[] getDomains() {
return domains;
}//getDomains()//
/**
* Sets the domains the web application services. These are exact match only, wild cards are not yet supported.
* @param domains The domains serviced.
*/
public void setDomains(String[] domains) {
this.domains = domains;
}//setDomains()//
public String[] getServices() {
return services;
}//getServices()//
/**
* Sets the web server services (defined when setting up the webserver) that this application uses.
* @param services The names of the services used by the application.
*/
public void setServices(String[] services) {
this.services = services;
}//setServices()//
public SslMapping[] getSslMappings() {
return sslMappings;
}//getSslMappings()//
/**
* Sets the SSL mappings used by the application.
* @param sslMappings Usually only one mapping between the domain(s) and a keystore.
*/
public void setSslMappings(SslMapping[] sslMappings) {
this.sslMappings = sslMappings;
}//setSslMappings()//
public File getBaseDirectory() {
return baseDirectory;
}//getBaseDirectory()//
/**
* Sets the base *web* directory for the web application, where all the static resources can be found. This should be null if unknown.
* @param baseDirectory The base directory, or null if the process's base directory should be used.
*/
public void setBaseDirectory(File baseDirectory) {
this.baseDirectory = baseDirectory;
}//setBaseDirectory()//
public File getExternalBaseDirectory() {
return externalBaseDirectory;
}//getExternalBaseDirectory()//
/**
* Sets the optional, externally located base *web* directory for the web application. The files located here are not part of the webapp's distribution.
* @param externalBaseDirectory The external base directory, or null if not used.
*/
public void setExternalBaseDirectory(File externalBaseDirectory) {
this.externalBaseDirectory = externalBaseDirectory;
}//setExternalBaseDirectory()//
public File getCacheBaseDirectory() {
return cacheBaseDirectory;
}//getCacheBaseDirectory()//
/**
* Sets the optional base cache directory for the web application. The files located here are not part of the webapp's distribution and are fully re-creatable.
* @param cacheBaseDirectory The cache base directory, or null if not used.
*/
public void setCacheBaseDirectory(File cacheBaseDirectory) {
this.cacheBaseDirectory = cacheBaseDirectory;
}//setCacheBaseDirectory()//
public boolean getCompressResponses() {
return compressResponses;
}//getCompressResponses()//
/**
* Sets whether responses should be compressed by default when possible. Certain resources that compress poorly will of course ignore this directive.
* @param compressResponses Whether responses that can be compressed should be by default.
*/
public void setCompressResponses(boolean compressResponses) {
this.compressResponses = compressResponses;
}//setCompressResponses()//
public IHashMap getNameSubstitutionMap() {
return nameSubstitutionMap;
}//getNameSubstitutionMap()//
/**
* Sets the mapping between old resource names and new ones.
* This is used to substitute just the ending name for another ending name.
* It is not recommended to use this.
* <p>TODO: Replace this with something that uses a regular expression like language.</p>
* @param nameSubstitutionMap
*/
public void setNameSubstitutionMap(LiteHashMap nameSubstitutionMap) {
this.nameSubstitutionMap = nameSubstitutionMap;
}//setNameSubstitutionMap()//
public IHashMap getPathSubstitutionMap() {
return pathSubstitutionMap;
}//getPathSubstitutionMap()//
/**
* Sets the mapping between old resource request paths and new ones.
* This is used to substitute a *full* resource requested by the client with another *full* resource request.
* For example: "/" -> "/index.html" will only alter http://mydomain.com/ to become http://mydomain.com/index.html, but won't modify http://mydomain.com/path/ at all.
* <p>TODO: Replace this with something that uses a regular expression like language.</p>
* @param pathSubstitutionMap
*/
public void setPathSubstitutionMap(LiteHashMap pathSubstitutionMap) {
this.pathSubstitutionMap = pathSubstitutionMap == null ? new LiteHashMap(10) : pathSubstitutionMap;
}//setPathSubstitutionMap()//
public String getDefaultPackageName() {
return defaultPackageName;
}//getDefaultPackageName()//
/**
* Sets the package fragment used as the base for all java class calls by the web client.
* The web client can request a resource such as "controller.Login.java" (anything ending in .java) and this package name will be pre-pended to create the complete class name.
* The class should implement IWebRequestHandler for the call to work.
* @param defaultPackageName The package name fragment prefixed to java calls (generally should end with a dot).
*/
public void setDefaultPackageName(String defaultPackageName) {
this.defaultPackageName = defaultPackageName;
}//setDefaultPackageName()//
public long getSessionTimeout() {
return sessionTimeout;
}//getSessionTimeout()//
/**
* Sets the amount of time (in milliseconds) before the session may be discarded. This defaults to 3600000 (1 hour).
* @param sessionTimeout The number of milliseconds before the session garbage collector can release the session and all related resources.
*/
public void setSessionTimeout(long sessionTimeout) {
this.sessionTimeout = sessionTimeout;
}//setSessionTimeout()//
public long getSessionCheckInterval() {
return sessionCheckInterval;
}//getSessionCheckInterval()//
/**
* Sets the number of milliseconds between checks to the sessions for stale ones. This defaults to 3600000 (1 hour).
* @param sessionCheckInterval The amount of time between checking for and discarding old sessions.
*/
public void setSessionCheckInterval(long sessionCheckInterval) {
this.sessionCheckInterval = sessionCheckInterval;
}//setSessionCheckInterval()//
public IBadSessionHandler getBadSessionHandler() {
return badSessionHandler;
}//getBadSessionHandler()//
/**
* ?
* @param badSessionHandler
*/
public void setBadSessionHandler(IBadSessionHandler badSessionHandler) {
this.badSessionHandler = badSessionHandler;
}//setBadSessionHandler()//
/**
* Adds a resource name subtitution mapping where the old resource name will be replaced by the new resource name automatically prior to the request being processed.
* @param oldName The old resource name.
* @param newName The new resource name.
*/
public void addResourceNameSubstitution(String oldName, String newName) {
nameSubstitutionMap.put(oldName, newName);
}//addResourceNameSubstitution()//
/**
* Adds a path subtitution mapping where the old path will be replaced by the new path automatically prior to the request being processed.
* @param oldPath The old resource name. eg: "/"
* @param newPath The new resource name. eg: "/subdir/index.html"
* @param forward Whether the substitution should constitute a forward (where the client is told of the new path) versus a redirect.
*/
public void addPathSubstitution(String oldPath, String newPath, boolean forward) {
pathSubstitutionMap.put(oldPath, new PathSubstitution(newPath, forward));
}//addPathSubstitution()//
/**
* Gets the resource request handler which allows the application control over how users access static resources via the web server.
* @return Replaces the default handling of a user's access to static (file system based) resources. Allows for permission validation.
*/
public IResourceRequestHandler getResourceRequestHandler() {
return resourceRequestHandler;
}//getResourceRequestHandler()//
/**
* Sets the resource request handler which allows the application control over how users access static resources via the web server.
* @param resourceRequestHandler Replaces the default handling of a user's access to static (file system based) resources. Allows for permission validation.
*/
public void setResourceRequestHandler(IResourceRequestHandler resourceRequestHandler) {
this.resourceRequestHandler = resourceRequestHandler;
}//setResourceRequestHandler()//
/**
* Gets the web dav request handler.
* @return Replaces the default handling of web dav commands.
*/
public IWebdavRequestHandler getWebdavRequestHandler() {
return webdavRequestHandler;
}//getWebdavRequestHandler()//
/**
* Sets the web dav request handler.
* @param webdavRequestHandler Replaces the default handling of web dav commands.
*/
public void setWebdavRequestHandler(IWebdavRequestHandler webdavRequestHandler) {
this.webdavRequestHandler = webdavRequestHandler;
}//setWebdavRequestHandler()//
public long getCacheCutoffSize() {
return cacheCutoffSize;
}//getCacheCutoffSize()//
/**
* Sets the cutoff size of a file qualifying it for caching.
* @param cacheCutoffSize The number of bytes a file may contain in order to qualify for caching in memory. A zero indicates no caching will be performed. A value of less than zero is invalid and will default to zero.
*/
public void setCacheCutoffSize(long cacheCutoffSize) {
this.cacheCutoffSize = cacheCutoffSize < 0 ? 0 : cacheCutoffSize;
}//setCacheCutoffSize()//
public IRetainRequestBufferHandler getRetainRequestBufferHandler() {
return retainRequestBufferHandler;
}//getRetainRequestBufferHandler()//
/**
* Gets the handler called by the web server when deciding whether to retain a request's full byte buffer (exactly what bytes make up the request as received). A null value will turn this feature off entirely.
* @param retainRequestBufferHandler The handler used by the webserver to determine when to save the incoming request bytes.
*/
public void setRetainRequestBufferHandler(IRetainRequestBufferHandler retainRequestBufferHandler) {
this.retainRequestBufferHandler = retainRequestBufferHandler;
}//setRetainRequestBufferHandler()//
}//WebOptions//

View File

@@ -0,0 +1,402 @@
/*
* Copyright (c) 2007,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.web;
import java.io.IOException;
import java.util.Date;
import com.common.debug.Debug;
import com.common.io.ByteArrayInputStream;
import com.common.io.ByteArrayOutputStream;
import com.common.io.IClassReplacementHandler;
import com.common.io.IExternalizable;
import com.common.io.IObjectInputStream;
import com.common.io.IObjectOutputStream;
import com.common.io.ObjectInputStream;
import com.common.io.ObjectOutputStream;
import com.common.io.StreamSupport;
import com.common.util.IIterator;
import com.common.util.LiteHashMap;
import com.common.util.StringSupport;
import com.common.util.optimized.IntObjectHashMap;
import com.foundation.event.IRequestHandler;
import com.foundation.transaction.Transaction;
import com.foundation.transaction.TransactionErrorInfo;
import com.foundation.web.controller.HtmlViewController;
import com.foundation.web.interfaces.ISession;
import com.foundation.web.model.SessionStore;
/**
* The session for a user's connection(s) to the server. This encompases all the data about the active user.
*/
public class WebSession implements ISession {
/** The identifier for the session. */
private String sessionId = null;
/** The identifier for the secure session data. This will be matched for each secure request to ensure we don't get spoofed. */
private String secureSessionId = null;
/** The session store used to update the db. */
private SessionStore sessionStore = null;
/** The data set by the application containing application specific session data. */
private SessionData applicationData = null;
/** The data set by the application containing application specific secure session data. */
private SecureSessionData applicationSecureData = null;
/** The timestamp for the last access of the session. */
private long lastAccessTime = System.currentTimeMillis();
/** The currently active client ID. Only one browser view can be active at a time to prevent miss use of server memory. */
private int currentClientId = 0;
/** The next available client ID. */
private int nextClientId = 1;
/** The mapping of stateful web views by view ID. */
private IntObjectHashMap frameworkViewMap = new IntObjectHashMap(20);
/** The ID counter for stateful web views created on the server (uses negative values, where as the views created by the client uses positive values). */
private int nextFrameworkViewId = -1;
/** The bytes of the secure session id. */
private byte[] secureSessionIdBytes;
/** The handler used by all views within this session to process requests to views on a single thread. */
private IRequestHandler requestHandler;
/** Whether the session has been flagged as changed and requiring an update in the repository. */
private boolean hasChanged = true;
/**
* WebSession constructor.<br/>
* <i>This constructor is to be called prior to calling restore(ByteArrayInputStream, IClassReplacementHandler) ONLY.</i>
* @param sessionId The session's identifier.
* @param sessionStore The model used to update the repository with the session information.
* @param requestHandler The handler used to thread all code that interacts with anything associated with the user's session.
*/
public WebSession(String sessionId, SessionStore sessionStore, IRequestHandler requestHandler) {
this.sessionId = sessionId;
this.sessionStore = sessionStore;
this.requestHandler = requestHandler;
}//WebSession()//
/**
* WebSession constructor.
* @param sessionId The session's identifier.
* @param sessionStore The model used to update the repository with the session information.
* @param applicationData The data set by the application and retained by the session.
* @param requestHandler The handler used to thread all code that interacts with anything associated with the user's session.
*/
public WebSession(String sessionId, SessionStore sessionStore, SessionData applicationData, IRequestHandler requestHandler) {
this.sessionId = sessionId;
this.sessionStore = sessionStore;
this.applicationData = applicationData;
this.requestHandler = requestHandler;
}//WebSession()//
/**
* Gets the model that represents the session in the repository.
* @return The repository storage model for the session.
*/
public SessionStore getSessionStore() {
return sessionStore;
}//getSessionStore()//
/**
* Gets the flag indicating whether the session data has changed in a significant way, requiring a client update.
* @return Whether the session data has been changed.
*/
public boolean hasChanged() {
return hasChanged;
}//hasChanged()//
/**
* Flags the session data as having changed in a significant way, requiring a repository update.
*/
public void markChanged() {
this.hasChanged = true;
}//markChanged()//
/* (non-Javadoc)
* @see com.foundation.web.interfaces.ISession#updateRepository()
*/
public void updateRepository() {
//Only store the session in the repository if it has been modified in a significant way, and it has attached session data (has been given a session id). Those without the session id's are web apps that don't track a session.//
if(hasChanged() && sessionId != null) {
ByteArrayOutputStream bout = new ByteArrayOutputStream(1000, false);
try {
TransactionErrorInfo info;
store(bout);
sessionStore.setSessionData(bout.toByteArray());
sessionStore.setUpdateTimestamp(new Date());
if(sessionStore.isObjectRepositoryBound()) {
info = sessionStore.entityUpdate(Transaction.DEBUG_ERRORS);
if(info == null) {
hasChanged = false;
}//if//
else {
Debug.log(info.toString());
if(info.getErrorException() != null) {
Debug.log(info.getErrorException());
}//if//
}//else//
}//if//
else {
hasChanged = false;
}//else//
}//try//
catch(Throwable e) {
Debug.log(e);
}//catch//
}//if//
}//updateRepository()//
/**
* Gets the request handler which threads all requests for the session.
* @return The handler used to ensure all requests that access shared resources (reflections) are single threaded, and is used to create a reflection context for each view.
*/
public IRequestHandler getRequestHandler() {
return requestHandler;
}//getRequestHandler()//
/**
* Gets the id of the current client. This will change if the user opens a new window or tab on the site, or if the user refreshes the site.
* @return The current client id.
*/
public int getCurrentClientId() {
return currentClientId;
}//getCurrentClientId()//
/**
* Sets the id of the current client. This will change if the user opens a new window or tab on the site, or if the user refreshes the site.
* @param currentClientId The current client id.
*/
public void setCurrentClientId(int currentClientId) {
if(currentClientId != this.currentClientId) {
//Release all the views.//
for(IIterator iterator = frameworkViewMap.valueIterator(); iterator.hasNext(); ) {
((HtmlViewController) iterator.next()).release();
}//for//
//Remove the views from the mapping.//
frameworkViewMap.removeAll();
//Set the new current client id.//
this.currentClientId = currentClientId;
}//if//
}//getCurrentClientId()//
/**
* Gets the next available client ID.
* @return The next available client ID.
*/
public int getNextClientId() {
//Flag the session data as having changed so the repository gets updated.//
markChanged();
return nextClientId++;
}//getNextClientId()//
/**
* Gets the view controller for the given view's ID.
* @return The stateful framework web view data for this session.
*/
public HtmlViewController getFrameworkView(int id) {
return (HtmlViewController) frameworkViewMap.get(id);
}//getFrameworkViewData()//
/**
* Registers the framework view for later access by id.
* @param id The view's ID.
* @param viewController The view's controller.
*/
public void registerFrameworkView(int id, HtmlViewController viewController) {
frameworkViewMap.put(id, viewController);
}//registerFrameworkView()//
/**
* Unregisters the framework view when it is being closed.
* @param id The view's ID.
* @return The view's controller.
*/
public HtmlViewController unregisterFrameworkView(int id) {
return (HtmlViewController) frameworkViewMap.remove(id);
}//unregisterFrameworkView()//
/**
* Gets the next framework view ID and increments the counter that ensures each one is unique within the session.
* @return The next framework view ID which can be used for a new view.
*/
public int getNextFrameworkViewId() {
return nextFrameworkViewId--;
}//getNextFrameworkViewId()//
/**
* Gets the timestamp for the last access of the session.
* @return The last time the session was retrieved.
*/
public long getLastAccessTime() {
return lastAccessTime;
}//getLastAccessTime()//
/**
* Sets the timestamp for the last access of the session.
* @param lastAccessTime The last time the session was retrieved.
*/
public void setLastAccessTime(long lastAccessTime) {
this.lastAccessTime = lastAccessTime;
}//setLastAccessTime()//
/* (non-Javadoc)
* @see com.foundation.web.interfaces.ISession#getSessionId()
*/
public String getSessionId() {
return sessionId;
}//getSessionId()//
/* (non-Javadoc)
* @see com.foundation.web.interfaces.ISession#getSecureSessionId()
*/
public String getSecureSessionId() {
return secureSessionId;
}//getSecureSessionId()//
/**
* Gets the bytes that make up the secure session id.
* @return The secure session id's bytes.
*/
byte[] getSecureSessionIdBytes() {
return secureSessionIdBytes;
}//getSecureSessionIdBytes()//
/* (non-Javadoc)
* @see com.foundation.web.interfaces.ISession#getIsLoggedIn()
*/
public boolean getIsLoggedIn() {
return getApplicationData() != null ? applicationData.isLoggedIn() : false;
}//getIsLoggedIn()//
/* (non-Javadoc)
* @see com.foundation.web.ISession#getApplicationData()
*/
public Object getApplicationData() {
return applicationData;
}//getApplicationData()//
/* (non-Javadoc)
* @see com.foundation.web.ISession#getApplicationData()
*/
public Object getApplicationSecureData() {
return applicationSecureData;
}//getApplicationSecureData()//
/**
* Sets up the secure session data and identifier.
* @param secureSessionId The ID for the secure session - used to prevent spoofing.
* @param applicationSecureData The appliation's data that will be passed to any request containing the secure session id and comming across a secure session.
*/
public void setupSecureSession(String secureSessionId, byte[] secureSessionIdBytes, SecureSessionData applicationSecureData) {
this.secureSessionId = secureSessionId;
this.applicationSecureData = applicationSecureData;
this.secureSessionIdBytes = secureSessionIdBytes;
}//setupSecureSession()//
/**
* Releases the session.
*/
public void release() {
if(applicationData != null) {
applicationData.release();
}//if//
if(applicationSecureData != null) {
applicationSecureData.release();
}//if//
}//release()//
/**
* Restores as best as possible the web session.
* @param bin The stream containing the previously stored bytes.
* @param handler Used to replace one class name with another prior to instantiation. This allows us to account for name changes without being unable to read a stream.
* @throws IOException
* @throws ClassNotFoundException
*/
public void restore(ByteArrayInputStream bin, IClassReplacementHandler handler) throws IOException, ClassNotFoundException {
/*int version = */bin.read();
ObjectInputStream in;
lastAccessTime = System.currentTimeMillis();
nextClientId = StreamSupport.readInt(bin);
secureSessionId = (secureSessionId = StreamSupport.readString(bin, "ASCII")).length() == 0 ? null : secureSessionId;
secureSessionIdBytes = secureSessionId != null ? StringSupport.base64Decode(secureSessionId) : null;
in = new ObjectInputStream(bin, null, handler, null, null, null);
try {
applicationData = (SessionData) in.readObject();
applicationData.initialize(this);
applicationSecureData = (SecureSessionData) in.readObject();
if(applicationSecureData != null) {
applicationSecureData.initialize(applicationData);
}//if//
}//try//
finally {
//Don't do this! Will cause the underlying byte input stream to close also.//
//if(in != null) in.close();
}//finally//
/*
for(int index = 0; index < viewCount; index++) {
int id = StreamSupport.readInt(bin);
int position = bin.getPosition();
int length = StreamSupport.readInt(bin);
String typeName = StreamSupport.readString(bin);
try {
Class type = getClass().getClassLoader().loadClass(typeName);
HtmlViewController controller = (HtmlViewController) type.newInstance();
((BaseHtmlViewController) controller).initialize(id, this, applicationData, applicationSecureData);
controller.read(new ObjectInputStream(bin, null, handler, null, null, null));
registerFrameworkView(id, controller);
}//try//
catch(Throwable e) {
Debug.log(e);
//Recover from the exception.//
bin.setPosition(position + length);
}//catch//
}//for//
*/
}//restore()//
/**
* Backs up the web session.
* @param bout
* @throws IOException
*/
public void store(ByteArrayOutputStream bout) throws IOException {
ObjectOutputStream out;
bout.write(0);
StreamSupport.writeInt(nextClientId, bout);
StreamSupport.writeString(secureSessionId == null ? "" : secureSessionId, bout, "ASCII");
//Note: If reading/writing these fail then the whole thing should fail.//
out = new ObjectOutputStream(bout, null);
try {
out.writeObject(applicationData);
out.writeObject(applicationSecureData);
}//try//
finally {
//Don't do this! Will close the underlying stream and clear any data.
// if(out != null) out.close();
}//finally//
/*
for(IIterator iterator = frameworkViewMap.valueIterator(); iterator.hasNext(); ) {
HtmlViewController next = (HtmlViewController) iterator.next();
int id = next.getDisplayId();
int position;
//Write the view's ID.//
StreamSupport.writeInt(id, bout);
//Save the position at which we should write the size of the view data.//
position = bout.getPosition();
try {
//Reserve space for the size of the view's output.//
bout.pad(4);
//Write the view controller class name.//
StreamSupport.writeString(next.getClass().getName(), bout);
//Write the view.//
next.write(new ObjectOutputStream(bout, null));
//Write the size of the view's output.//
StreamSupport.writeInt(bout.getSize() - position - 4, bout.getBuffer(), position);
viewCount++;
}//try//
catch(Throwable e) {
Debug.log(e);
//Throw out any data written to the stream by the offending view controller.//
bout.setPosition(position - 4);
}//catch//
}//for//
//Write the view count.//
StreamSupport.writeInt(viewCount, bout.getBuffer(), viewCountPosition);
*/
}//store()//
}//WebSession//

View File

@@ -0,0 +1,321 @@
/*
* Copyright (c) 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.web;
import com.common.util.*;
import com.common.debug.*;
import com.common.thread.*;
import com.foundation.event.*;
/*
* The request handler used by the web session to thread all request processing, response sending, and reflection updates through a single thread.
*/
public class WebSessionRequestHandler implements Runnable, IRequestHandler, ISingleThreadedContext {
/** The queue that all requests are sent to. Requests made on the event thread are handled immediatly and are never queued. */
private Queue queue = new Queue(5);
/** The thread currently handing requests. */
private Thread queueRunner = null;
/** Whether the loop is running. */
private boolean isRunning = false;
/**
* Maintains request information and handles notification of completion.
*/
private static final class Request implements Runnable {
private IRunnable runnable = null;
private Object returnValue = null;
private boolean requestComplete = false;
/**
* Request constructor.
*/
private Request(IRunnable runnable) {
this.runnable = runnable;
}//Request()//
public synchronized void waitForCompletion() {
while(!requestComplete) {
try {
wait(1000);
}//try//
catch(InterruptedException e) {
Debug.handle(e);
}//catch//
}//while//
}//waitForCompletion()//
public void run() {
try {
returnValue = runnable.run();
}//try//
catch(Throwable e) {
Debug.log("Failed to complete the processing of a web view request.", e);
}//catch//
synchronized(this) {
requestComplete = true;
notifyAll();
}//synchronized//
}//run()//
}//Request//
/**
* Encapsulates an event thread which has reliquished its status for a time while it performs some unrelated processing which may take an arbitrary amount of time.
*/
public class EventThread {
/** Whether the held thread is once again the event thread. */
private boolean isEventThread = false;
/** The thread which requested a pause in it being the event thread. */
private Thread eventThread = null;
/**
* EventThread constructor.
*/
public EventThread() {
this.eventThread = Thread.currentThread();
}//EventThread()//
/**
* Requests that the calling thread become the event thread once more.
*/
public void requestResumeEventProcessing() {
processRequest(this);
}//resumeEventProcessing()//
/**
* Waits for the current event thread to make us the event thread.
*/
public void waitToResumeEventProcessing() {
//A little bit of error checking.//
if(eventThread != Thread.currentThread()) {
throw new IllegalArgumentException("Only the former event thread may request it become the event thread again.");
}//if//
//Synchronize so we can wait to be called on.//
synchronized(WebSessionRequestHandler.this) {
//Wait while we have not been called on to become the event thread again.//
while(!isEventThread) {
try {
WebSessionRequestHandler.this.wait(3000);
}//try//
catch(InterruptedException e) {
Debug.handle(e);
}//catch//
}//while//
}//synchronized//
ThreadService.setIsInSingleThreadedContext(WebSessionRequestHandler.this);
}//waitToResumeEventProcessing()//
/**
* Called by the current event thread to yield event processing to the held thread.
* <p>Note: The caller must hold a synch lock on the EventLoop object.</p>
*/
private void notifyEventThread() {
//Let the proper thread know it is now the event thread.//
isEventThread = true;
//Assign the new queue runner to this thread.//
queueRunner = eventThread;
//Notify all waiting threads.//
WebSessionRequestHandler.this.notifyAll();
}//notifyEventThread()//
}//EventThread//
/**
* WebSessionRequestHandler constructor.
*/
public WebSessionRequestHandler() {
}//WebSessionRequestHandler()//
/**
* Runs the request handler.
* <p>The handler is designed to run until there are no more requests, then it will quit. When new requests arrive they will start the handler if it is not already running.</p>
*/
public void run() {
boolean stop = false;
boolean isEventThread = true;
ThreadService.setIsInSingleThreadedContext(this);
isRunning = true;
while((isEventThread) && (!stop)) {
try {
Object next = null;
do {
next = null;
//Get the next runnable.//
synchronized(this) {
if(queue.getSize() > 0) {
//Check the message queue.//
next = queue.dequeue();
}//if//
}//synchronized//
if(next != null) {
if(next instanceof EventThread) {
EventThread eventThread = (EventThread) next;
synchronized(this) {
//Notify the event thread that it may now take over as the event thread again.//
eventThread.notifyEventThread();
}//synchronized//
//Set the flag to immediatly quit processing messages as the event thread.//
isEventThread = false;
}//if//
else {
((Runnable) next).run();
}//else//
}//if//
} while((isEventThread) && (!stop) && (next != null));
if((isEventThread) && (!stop)) {
synchronized(this) {
if(queue.getSize() == 0) {
//Wait a very short time to see if new messages are queued or event threads ready to restart. If not then reclaim this thread.//
try {
wait(100);
}//try//
catch(Throwable e) {
Debug.handle(e);
}//catch//
if(queue.getSize() == 0) {
stop = true;
queueRunner = null;
}//if//
}//if//
}//synchronized//
}//if//
}//try//
catch(Throwable e) {
Debug.log(e);
//Keep processing messages so the client might not lock up.//
}//catch//
}//while//
ThreadService.setIsInSingleThreadedContext(null);
isRunning = false;
}//run()//
/**
* Pauses the calling thread such that it is no longer the event thread and may block without affecting the event processing system.
* When the calling thread is ready to resume being the event thread then it must call the returned EventThread object's resumeEventProcessing() method which will block until the calling thread is once again the event thread.
* <p>Warning: The EventThread object should never be passed to another thread since it will corrupt the EventLoop state.</p>
* @return The EventThread instance which is necessary for resuming the mantel of event thread when the thread has finished waiting.
*/
public EventThread pauseEventProcessing() {
EventThread result = new EventThread();
ThreadService.setIsInSingleThreadedContext(this);
synchronized(this) {
//Ensure that all know we are no longer the event thread.//
queueRunner = null;
//If necessary, start another thread to be the event thread.//
if(queue.getSize() != 0) {
queueRunner = ThreadService.run(this, false);
}//if//
}//synchronized//
return result;
}//pauseEventProcessing()//
/**
* Processes a request through the event thread. This is required to access any SWT method due to the design of SWT.
* @param request The object that will be run when the request is processed.
* @param synchronous Whether the thread should block until the request has been processed (should be true if a result is desired).
*/
public void processRequest(final Runnable request, boolean synchronous) {
execute(new IRunnable() {
public Object run() {
request.run();
return null;
}//run()//
}, synchronous);
}//processRequest()//
/**
* Processes a request through the event thread.
* @param request The request to be processed.
*/
private synchronized void processRequest(Runnable request) {
queue.enqueue(request);
if(queueRunner == null) {
//Start the QueueRunner thread.//
queueRunner = ThreadService.run(this, false);
}//if//
}//processRequest()//
/**
* Processes a request through the event thread.
* @param request The request to be processed.
*/
private synchronized void processRequest(EventThread request) {
queue.enqueue(request);
if(queueRunner == null) {
//Start the QueueRunner thread.//
queueRunner = ThreadService.run(this, false);
}//if//
}//processRequest()//
/* (non-Javadoc)
* @see com.foundation.event.IRequestHandler#isRunning()
*/
public boolean isRunning() {
return isRunning;
}//isRunning()//
/* (non-Javadoc)
* @see com.foundation.event.IRequestHandler#isRequestThread()
*/
public boolean isRequestThread() {
Thread currentThread = Thread.currentThread();
//This synchronization is necessary in order to ensure the local processor cache is updated.//
synchronized(this) {
return queueRunner == currentThread;
}//synchronized//
}//isRequestThread()//
/* (non-Javadoc)
* @see com.foundation.event.IRequestHandler#execute(com.common.thread.IRunnable, boolean)
*/
public Object execute(IRunnable runnable, boolean synchronous) {
Object result = null;
boolean isQueueRunner = false;
Thread currentThread = Thread.currentThread();
//This synchronization is necessary in order to ensure the local processor cache is updated.//
synchronized(this) {
isQueueRunner = queueRunner == currentThread;
}//synchronized//
//Make sure we don't have the event thread creating requests since it would deadlock the system.//
if(!isQueueRunner || !synchronous) {
Request request = new Request(runnable);
processRequest(request);
if(synchronous) {
request.waitForCompletion();
result = request.returnValue;
}//if//
}//if//
else {
//The calling thread is the event thread, so there is no need to pass the request through the event thread!.//
result = runnable.run();
}//else//
return result;
}//execute()//
/* (non-Javadoc)
* @see com.foundation.event.IRequestHandler#execute(com.common.thread.IRunnable)
*/
public Object execute(IRunnable runnable) {
return execute(runnable, true);
}//execute()//
/* (non-Javadoc)
* @see com.foundation.event.IRequestHandler#executeAsync(com.common.thread.IRunnable)
*/
public void executeAsync(IRunnable runnable) {
execute(runnable, false);
}//executeAsync()//
}//WebSessionRequestHandler//

View File

@@ -0,0 +1,79 @@
package com.foundation.web.controller;
import java.io.IOException;
import com.common.io.IObjectInputStream;
import com.common.io.IObjectOutputStream;
import com.common.util.StringSupport;
import com.foundation.attribute.ReflectionContext;
import com.foundation.controller.Controller;
import com.foundation.web.BaseHtmlViewController;
import com.foundation.web.JsonContent;
import com.foundation.web.WebSession;
import com.foundation.web.interfaces.IRequest;
import com.foundation.web.interfaces.IResponse;
import com.foundation.web.SecureSessionData;
import com.foundation.web.SessionData;
/**
* Defines the minimum requirements of an HTML based view controller.
* All application stateful HTML view controllers should extend this class.
*/
public abstract class HtmlViewController extends BaseHtmlViewController {
/**
* HtmlViewController constructor.
*/
public HtmlViewController() {
}//HtmlViewController()//
/**
* HtmlViewController constructor.
* @param parent The view controller that is creating this view controller.
*/
public HtmlViewController(HtmlViewController parent) {
super(parent);
}//HtmlViewController()//
/**
* Gets the view content.
* @return The html content.
*/
public abstract String getView();
/**
* Provides the request handler an opportunity to process the request.
* @param query The query identifier sent by the client indicating what kind of request this is.
* @param request The request metadata.
* @param response The response metadata.
* @param sessionData The application specific data object associated with this user's session.
* @param secureSessionData The application specific data object associated with this user's session if the user made the request over a secure connection and used the correct secure session identifier, otherwise null.
*/
public abstract void processRequest(String query, IRequest request, IResponse response, SessionData sessionData, SecureSessionData secureSessionData);
/**
* Builds a response for a request that generates a new view.
* <p>This response is designed to be processed by the framework javascript on the client as a response to the callView method.</p>
* @param sessionData The session that the request belongs to.
* @param controller The controller for the new view.
* @param result The optional request result that is passed along with the new view data.
* @param releaseScript The script run when the view is closed.
* @return The text for the response.
*/
public JsonContent buildResponse(HtmlViewController controller) {
StringBuffer buffer = new StringBuffer(1000);
buffer.append("{\"displayId\": \"").append(controller.getDisplayId()).append("\"");
buffer.append(", \"view\": \"").append(encodeFramework(controller.getView())).append("\"");
return new JsonContent(buffer.append("}").toString());
}//buildResponse()//
/**
* Reads the view controller from the stream. Exceptions thrown will not affect the restoration of other views.
* @param in The stream containing just this view's data.
* @throws IOException
* @throws ClassNotFoundException
*/
public abstract void read(IObjectInputStream in) throws IOException, ClassNotFoundException;
/**
* Writes the view to the stream. Exceptions thrown will not affect the storage of other views.
* @param out The stream to write to.
* @throws IOException
*/
public abstract void write(IObjectOutputStream out) throws IOException;
}//HtmlViewController//

View File

@@ -0,0 +1,103 @@
package com.foundation.web.controller;
import java.io.File;
import java.util.Date;
import com.common.debug.Debug;
import com.common.io.StreamSupport;
import com.common.util.StringSupport;
import com.foundation.controller.Controller;
import com.foundation.web.BaseWebController;
import com.foundation.web.FileContent;
import com.foundation.web.IWebRequestHandler;
import com.foundation.web.interfaces.ICachedResource;
import com.foundation.web.interfaces.IRequest;
import com.foundation.web.interfaces.IWebApplication;
/**
* Encapsulates some functionality around the IWebRequestHandler.
* A web request controller is a stateless HTML view controller.
*/
public abstract class WebRequestController extends BaseWebController implements IWebRequestHandler {
/**
* WebRequestController constructor.
*/
public WebRequestController() {
}//WebRequestController()//
/**
* Checks the given text file to see if there are any changes from the request's cache date.
* @param request The request used as a context for the call.
* @param relativePath The path relative to this controller's path (from the default package for the web app) translated into the web resource base directory. For example 'test.html': if the default package name for the web app is com.myco.web and this controller resides in com.myco.web.session and the web app's base directory is ./web then the resource test.html would be found at './web/session/test.html'.
* @return Whether the local file is newer than the cache date provided to the client <b>(true if the client needs the new content)</b>.
*/
public boolean checkTextFile(IRequest request, String relativePath) {
boolean result = true;
if(request.getCacheDate() != null) {
IWebApplication application = request.getWebApplication();
File file = new File(application.getBaseDirectory(), getClass().getPackage().getName().substring(request.getWebApplication().getDefaultPackageName().length()).replace('.', File.separatorChar) + File.separatorChar + relativePath);
result = new Date(file.lastModified() & 0xFFFFFFFFFFFF0000L).after(request.getCacheDate());
}//if//
return result;
}//checkTextFile()//
/**
* Loads text from a file given the request and a path relative to this controller using the web application's cache where possible.
* @param request The request used as a context for the call.
* @param relativePath The path relative to this controller's path (from the default package for the web app) translated into the web resource base directory. For example 'test.html': if the default package name for the web app is com.myco.web and this controller resides in com.myco.web.session and the web app's base directory is ./web then the resource test.html would be found at './web/session/test.html'.
* @param encoding The text encoding for the file. If null then the default will be used (UTF8).
* @return The text for the resource or null if not found or not decodeable.
*/
public String loadTextFile(IRequest request, String relativePath, String encoding) {
String result = null;
try {
IWebApplication application = request.getWebApplication();
File baseDirectory = application.getBaseDirectory();
IWebApplication webApplication = request.getWebApplication();
String defaultPackageName = webApplication.getDefaultPackageName();
String packageName = getClass().getName();
File file;
ICachedResource cache = null;
//Calculate the package name - note: for some reason the use of multiple class loaders is causing problems with simply querying the class object for the package.//
packageName = packageName.substring(0, packageName.lastIndexOf(getClass().getSimpleName()));
//Remove the ending dot.//
if(packageName.endsWith(".")) {
packageName = packageName.substring(0, packageName.length() - 1);
}//if//
file = new File(baseDirectory, (defaultPackageName.length() > packageName.length() ? "" : packageName.substring(defaultPackageName.length()).replace('.', File.separatorChar) + File.separatorChar) + relativePath);
if(application.isCachingResources()) {
try {
String canonicalPath = file.getCanonicalPath();
cache = application.getCachedResource(canonicalPath);
if(cache == null && file.exists() && file.canRead() && file.isFile()) {
byte[] bytes = StreamSupport.readBytes(file);
application.setCachedResource(canonicalPath, file, bytes);
result = new String(bytes, encoding == null ? "UTF8" : encoding);
}//if//
}//try//
catch(Throwable e) {
Debug.log(e);
}//catch//
}//if//
else if(file.exists() && file.canRead() && file.isFile()) {
byte[] bytes = StreamSupport.readBytes(file);
result = new String(bytes, encoding == null ? "UTF8" : encoding);
}//else if//
}//try//
catch(Throwable e) {
Debug.log(e);
}//catch//
return result;
}//loadTextFile()//
}//WebRequestController//

View File

@@ -0,0 +1,140 @@
package com.foundation.web.model;
import java.util.Date;
import com.foundation.application.IApplication;
import com.foundation.model.Model;
import com.foundation.metadata.Attribute;
/**
* Used to store the session data in the repository for the application.
* Sample SQL to setup the table:
<pre>CREATE TABLE `doyd`.`session_store` (
`server_id` INTEGER UNSIGNED NOT NULL,
`session_id` BIGINT UNSIGNED NOT NULL,
`sequence_id` SMALLINT UNSIGNED NOT NULL,
`session_data` BLOB,
`update_timestamp` TIMESTAMP,
PRIMARY KEY (`server_id`, `session_id`, `sequence_id`)
)
ENGINE = InnoDB;</pre>
* Sample repository metadata:
<pre>&lt;class name='com.foundation.web.model.SessionStore' repository-name='SESSION_STORE'&gt;
&lt;attribute name='serverId' type='Integer' repository-name='SERVER_ID' repository-type='INT' is-key='true' is-auto-generated='false'/&gt;
&lt;attribute name='sessionId' type='Long' repository-name='SESSION_ID' repository-type='BIGINT' is-key='true' is-auto-generated='false'/&gt;
&lt;attribute name='sequenceId' type='Short' repository-name='SEQUENCE_ID' repository-type='SMALLINT' is-key='true' is-auto-generated='false'/&gt;
&lt;attribute name='sessionData' type='byte[]' repository-name='SESSION_DATA' repository-type='BLOB'/&gt;
&lt;attribute name='updateTimestamp' type='java.util.Date' repository-name='UPDATE_TIMESTAMP' repository-type='TIMESTAMP'/&gt;
&lt;properties/&gt;
&lt;/class&gt;</pre>
*/
public class SessionStore extends Model {
public static final Attribute SERVER_ID = registerAttribute(SessionStore.class, "serverId");
public static final Attribute SESSION_ID = registerAttribute(SessionStore.class, "sessionId");
public static final Attribute SEQUENCE_ID = registerAttribute(SessionStore.class, "sequenceId");
public static final Attribute SESSION_DATA = registerAttribute(SessionStore.class, "sessionData");
public static final Attribute UPDATE_TIMESTAMP = registerAttribute(SessionStore.class, "updateTimestamp");
private IApplication application = null;
/**
* SessionStore constructor.
*/
public SessionStore() {
}//SessionStore()//
/**
* SessionStore constructor.
*/
public SessionStore(IApplication application) {
this.application = application;
}//SessionStore()//
/**
* SessionStore constructor.
*/
public SessionStore(IApplication application, Integer serverId, Long sessionId, Short sequenceId) {
this.application = application;
setServerId(serverId);
setSessionId(sessionId);
setSequenceId(sequenceId);
}//SessionStore()//
/* (non-Javadoc)
* @see com.foundation.common.Entity#getApplication()
*/
public IApplication getApplication() {
return application;
}//getApplication()//
/**
* Sets the application providing services to this model.
* @param application The application providing services.
*/
public void setApplication(IApplication application) {
this.application = application;
}//setApplication()//
/**
* Gets the serverId value.
* @return The serverId value.
*/
public Integer getServerId() {
return (Integer) getAttributeValue(SERVER_ID);
}//getServerId()//
/**
* Sets the serverId value.
* @param serverId The serverId value.
*/
public void setServerId(Integer serverId) {
setAttributeValue(SERVER_ID, serverId);
}//setServerId()//
/**
* Gets the sessionId value.
* @return The sessionId value.
*/
public Long getSessionId() {
return (Long) getAttributeValue(SESSION_ID);
}//getSessionId()//
/**
* Sets the sessionId value.
* @param sessionId The sessionId value.
*/
public void setSessionId(Long sessionId) {
setAttributeValue(SESSION_ID, sessionId);
}//setSessionId()//
/**
* Gets the sequenceId value.
* @return The sequenceId value.
*/
public Short getSequenceId() {
return (Short) getAttributeValue(SEQUENCE_ID);
}//getSequenceId()//
/**
* Sets the sequenceId value.
* @param sequenceId The sequenceId value.
*/
public void setSequenceId(Short sequenceId) {
setAttributeValue(SEQUENCE_ID, sequenceId);
}//setSequenceId()//
/**
* Gets the sessionData value.
* @return The sessionData value.
*/
public byte[] getSessionData() {
return (byte[]) getAttributeValue(SESSION_DATA);
}//getSessionData()//
/**
* Sets the sessionData value.
* @param sessionData The sessionData value.
*/
public void setSessionData(byte[] sessionData) {
setAttributeValue(SESSION_DATA, sessionData);
}//setSessionData()//
/**
* Gets the updateTimestamp value.
* @return The updateTimestamp value.
*/
public Date getUpdateTimestamp() {
return (Date) getAttributeValue(UPDATE_TIMESTAMP);
}//getUpdateTimestamp()//
/**
* Sets the updateTimestamp value.
* @param updateTimestamp The updateTimestamp value.
*/
public void setUpdateTimestamp(Date updateTimestamp) {
setAttributeValue(UPDATE_TIMESTAMP, updateTimestamp);
}//setUpdateTimestamp()//
}//SessionStore//