Diego Bravo Estrada - AMERICATI.COM

For version 24.01.29

Intro

AWF is a web framework oriented toward intranet heavy duty screens, typically composed by forms and display views. Features:

  • Screen oriented session: with a single web browser process, the user may login more than once establishing totally independent sessions (upon distinct web browser windows or tabs), usually to process several tasks in parallel; also, those tasks sometimes require distinct user profiles (which may also involve distinct login parameters or credentials)

  • Application driven screen sequence: no arbitrary jumps into user provided URLs, so no random access to unauthorized screens (i.e. no Word Wide Web navigation model)

  • Built-in protection against CRSF attacks: CRSF tokens automatically generated per displayed screen

  • Built-in mitigation against XSS attacks: small Javascript core avoiding XSS-vulnerable methods

  • Rapid development API for screen designers, oriented to solve the business requirements minimizing web (re)design time

  • Fairly independent from the web presentation technology, which allows for total customization of the visuals, leveraging the power of modern styling technologies

  • Flexible support for field validation based in regular expressions

  • Support for (multiple) file uploads and file downloads

  • Minimal standard dependencies: Java >= 8, JEE or Jakarta Servlet runtime, Jackson data-binding and Reflections class scanning

  • Runs in a managed environment (WAR: tested in Apache Tomcat 10) or as a standalone application (JAR: tested with Jetty 11 and Tomcat embedded 10.0 and Tomcat embedded 9.0)

AWF is implemented as a single instance Servlet com.americati.awf.AwfServlet.

The Guide

Developing a Web Application

In this section we’ll explain how to develop a sample web application with AWF. This application will be deployed as a war file for a Jakarta web container, but the distribution provides samples for embedded web apps.

We’ll assume that the AWF jar is installed in the user’s local maven repository. If it is not the case, it may be installed with:

mvn install:install-file -Dfile=awf-x.y.z.jar

For a JEE version, use the awf-jee artifact:

mvn install:install-file -Dfile=awf-jee-x.y.z.jar

Create a Web Project

We’ll create a standard maven web project using the archetype maven webapp.

$ mvn archetype:generate -DarchetypeGroupId=org.apache.maven.archetypes \
-DarchetypeArtifactId=maven-archetype-webapp -DarchetypeVersion=1.4 \
-DgroupId=org.acme -DartifactId=demo-awf -Dversion=0.0.1
...
$ mkdir -p demo-awf/src/main/java demo-awf/src/main/resources
$ mkdir -p demo-awf/src/test/java demo-awf/src/test/resources
$ find demo-awf/
demo-awf/
demo-awf/pom.xml
demo-awf/src
demo-awf/src/main
demo-awf/src/main/webapp
demo-awf/src/main/webapp/index.jsp
demo-awf/src/main/webapp/WEB-INF
demo-awf/src/main/webapp/WEB-INF/web.xml
demo-awf/src/main/resources
demo-awf/src/main/java
demo-awf/src/test
demo-awf/src/test/resources
demo-awf/src/test/java

The last line of the mvn command invocation must be adapted with suitable values.

Setup dependency

Next, the pom.xml signals Java >= 8 and adds the AWF dependency:

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
  <modelVersion>4.0.0</modelVersion>
  <groupId>org.acme</groupId>
  <artifactId>demo-awf</artifactId>
  <version>0.0.1</version>
  <packaging>war</packaging>
  <name>demo-awf Maven Webapp</name>
  <properties>
    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    <maven.compiler.source>1.8</maven.compiler.source>
    <maven.compiler.target>1.8</maven.compiler.target>
  </properties>
  <dependencies>
    <dependency>
      <groupId>junit</groupId>
      <artifactId>junit</artifactId>
      <version>4.11</version>
      <scope>test</scope>
    </dependency>
    <dependency>
      <groupId>com.americati</groupId>
      <artifactId>awf</artifactId>
      <version>x.y.z</version>
    </dependency>
  </dependencies>
</project>

This should be enough to build a (non functional) war with mvn install.

Configuring the AWF Servlet

It is needed to edit the demo-awf/src/main/webapp/WEB-INF/web.xml file in order to replace its contents with:

<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xmlns="https://jakarta.ee/xml/ns/jakartaee" xmlns:web="http://xmlns.jcp.org/xml/ns/javaee"
	xsi:schemaLocation="https://jakarta.ee/xml/ns/jakartaee
	https://jakarta.ee/xml/ns/jakartaee/web-app_5_0.xsd
	http://xmlns.jcp.org/xml/ns/javaee
	http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd" metadata-complete="false" version="5.0">
  <display-name>Web Test</display-name>
   <servlet>
    <servlet-name>awf</servlet-name>
    <init-param>
      <param-name>screen-package</param-name>
      <param-value>org.acme</param-value>
    </init-param>
    <init-param>
      <param-name>development</param-name>
      <param-value>1</param-value>
    </init-param>
  </servlet>
</web-app>
  1. The Web Application descriptor is updated to the Jakarta Servlet 5.0 spec. If the servlet container does not support the jakarta. packages, use the Jakarta Servlet 4.0 or JEE Servlet 3.x specs in order to employ the javax. package, and replace the awf dependency for awf-jee.

  2. The servlet named "awf" is configured

  3. The screen-package initialization parameter is provided with a package name for class scanning as we’ll see in the next section

  4. The optional development initialization parameter set to 1 enables debug messages via slf4j in the server, disabling caching of the static resources (like screen templates), and the emission of debug information in the browser console; for production environments this parameter should be removed or set to zero

  5. There are many AwfServlet initialization parameters; they are listed at the end of this document

Apache Tomcat embedded

Tested with version 10.0.23:

import org.apache.catalina.Context;
import org.apache.catalina.Wrapper;
import org.apache.catalina.servlets.DefaultServlet;
import org.apache.catalina.startup.Tomcat;

import com.americati.awf.AwfServlet;

public class EmbeddedTomcatTest {

 Tomcat tomcat;

 public void startServer(String resourceFolder) throws Exception {
                tomcat = new Tomcat();
                tomcat.getConnector();
                tomcat.setPort(8080);
                String rootPath = EmbeddedTomcat.class.getClassLoader().getResource(resourceFolder).getFile();
                tomcat.setBaseDir(rootPath);
                Context context = tomcat.addContext("/", rootPath);
                Wrapper servletWrapper = tomcat.addServlet("/", "awf", new AwfServlet());
                servletWrapper.addInitParameter(AwfServlet.IP_DEVEL, "1");
                servletWrapper.addInitParameter(AwfServlet.IP_BEAN_PACKAGE, "org.acme.beans,org.acme.morebeans");
                servletWrapper.addInitParameter(AwfServlet.IP_SCREEN_PACKAGE, "org.acme.screens");
                servletWrapper.addInitParameter(AwfServlet.IP_EXIT_LOGOUT, "exit/logout.html");
                servletWrapper.addInitParameter(AwfServlet.IP_TIMEOUT_SECONDS, "7200"); // 2 hours
                servletWrapper.setLoadOnStartup(1);
                context.addServletMappingDecoded("", "awf");
                context.addServletMappingDecoded("/awf", "awf");
                context.addServletMappingDecoded("/awfres/*", "awf");
                tomcat.addServlet("/", "default", new DefaultServlet());
                context.addServletMappingDecoded("/", "default");
                tomcat.start();
        }

        public void stopServer() throws Exception {
                tomcat.stop();
        }

Eclipse Jetty embedded

Tested with version 11.0.12:

import org.eclipse.jetty.server.*;
import org.eclipse.jetty.servlet.DefaultServlet;
import org.eclipse.jetty.servlet.ServletContextHandler;
import org.eclipse.jetty.servlet.ServletHolder;
import org.eclipse.jetty.util.ssl.SslContextFactory;

import com.americati.awf.AwfServlet;

public class EmbeddedJettyTest {

        public static void main(String[] args) throws Exception {
                Server server = new Server(8080);
                String rootPath = Main.class.getClassLoader().getResource("static").toString();
                ServletContextHandler context = new ServletContextHandler();
                context.setContextPath("/");
                context.setResourceBase(rootPath);
                server.setHandler(context);
                ServletHolder holder = new ServletHolder(new AwfServlet());
                holder.setInitOrder(0); // order and init on startup
                holder.setInitParameter(AwfServlet.IP_DEVEL, "1");
                holder.setInitParameter(AwfServlet.IP_BEAN_PACKAGE, "com.americati.awfemb.beans");
                holder.setInitParameter(AwfServlet.IP_SCREEN_PACKAGE, "com.americati.awfemb.screens");
                holder.setInitParameter(AwfServlet.IP_EXIT_LOGOUT, "exit/logout.html");
                holder.setInitParameter(AwfServlet.IP_TIMEOUT_SECONDS, "7200"); // 2 hours
                context.addServlet(holder, "");
                context.addServlet(holder, "/awf");
                context.addServlet(holder, "/awfres/*");
                ServletHolder holderDef = new ServletHolder("default", DefaultServlet.class);
                holderDef.setInitParameter("dirAllowed", "false");
                context.addServlet(holderDef, "/");
                context.setProtectedTargets(new String[] { "/WEB-INF" });
                server.start();
        }

Developing screens

An AWF screen is composed by two components: a template view (usually an HTML document) and a controlling object whose class is annotated with @AwfScreen.

We’ll create two screens for this demonstration: a "login" screen, and a "welcome" screen which will be the first (and the only) screen which the user will be able to access after a successful login.

The login view

Our login screen view will be a basic HTML document (the developer may add any client side technology like Jquery-ui, Bootstrap, etc.):

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
</head>
<body>
<h1>Login</h1>
User: <input data-awf="name" type='text'><br>
Pass: <input data-awf="pass" type='password'><br>
<input type="button" data-awf="u-login" value="Login">
</body>

This file will be located in demo-awf/src/main/webapp/WEB-INF/template/login.html; the /WEB-INF/template/ is the standard "prefix location" for AWF screen views. The view content is extracted by the framework using ServletContext.getResource() with this path.

Note
This prefix location may be changed with the view-resource-prefix servlet initialization parameter.

Each view is parsed by a "template processor" which transparently "injects" the AWF Javascript code at runtime.

Note
The template view processor class may be configured by the templateProcessor attribute of the @AwfScreen annotation. By default it uses the built-in DefScreenTplProc class.

In AWF the "interactive" HTML elements (widgets) must have the data-awf attribute. Such attribute is used to interface with the controlling screen code (to be explained below.)

Now the welcome.html view:

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
</head>
<body>
<h1>Welcome to the system</h1>
<input type='text' data-awf='hora'>
<input type="button" data-awf="actx" value="Other..."></input>
<button data-awf="to-page-1">Go to Page 1</button>
<button data-awf="u-logout">Logout</button>
</body>

Screen logical Name

Each screen of the application must have a unique "logical" name which is defined in the screen controller’s @AwfScreen annotation. Below we’ll define the "login" and "welcome" screens whose views were just presented.

Every application must define a screen named "login" which will be used as its entry point.

Screen controllers

The Screen controlling classes must be annotated with @AwfScreen and also must contain at least one event handler method annotated with @AwfHandle:

@AwfScreen(name = "login", view = "login.html")
public class ScrLogin {

	AwfTextbox name;
	AwfTextbox pass;

	@AwfHandle("u-login")
	public void work() {
		String validUser = "admin", validPass = "1234"; // dummy credentials
		if(validUser.equals(name.getText()) && validPass.equals(pass.getText()))
			AwfCmd.auth(name.getText(), "welcome");
		else AwfCmd.logout("exit/bad_login.html");
	}
}
Screen rules
  1. The "logical" screen name is defined in the @AwfScreen annotation’s name attribute. The view file name is defined with the view attribute. The widgets are fields whose name matches the view’s data-awf attribute previously described; optionally the @AwfWidget field annotation allows such fields to point to an arbitrary widget in the view

  2. The event handler @AwfHandle annotated method is called when the user requests some action from the screen (usually, clicking buttons.) The annotation supports the action String using @AwfHandle(action) for finer control (defaults to the * "catch-all" action.) Also, this method is called when the screen in just displayed (because a requested "jump" from a previous screen.) This method may have registered singleton beans as its parameters.

  3. The AwfCmd has static methods which provide useful information regarding the current request. It also have methods with allow to signal the request outcome, like auth and logout in the example.

  4. In the "user request" case, the event handler method’s request action comes from the data-awf attribute value in an HTML element of the view. If such attribute has the form val1:val2 then the val1 is the action String parameter and val2 is the arg extra argument to be extracted with AwfCmd.arg().

  5. The "user requests" are issued from any button or input type='button' HTML elements with the data-awf attribute. Also, from "clickable" HTML elements having a data-awf attribute which starts with the prefix u- (this is a common pattern for hyperlinks.). Else the HTML element’s text may be accessed as AWF widgets but will not fire a request on click.

  6. The AwfCmd static methods are used to signal the desired outcome for the handler method. The go()/gosub() methods instruct AWF for "jumping" to a "destination" screen specifying its logical name, optionally specifying the destination screen controller’s arg parameter. The auth() methods are valid only in the login screen to signal a successful authentication and setup a new screen session.

  7. After a jump (go(), gosub() and ret() requests) the destination page fetched and its controller receives a Awf.ACTION_JUMP="jump" action.

  8. The AwfCmd.previousPage() method provides the name of the previously displayed screen.

  9. The "session" is associated with the browser window or tab where the login was executed; other windows or tabs of the same browser do not inherit the session (unlike most web applications.)

  10. When the user issues some "unavoidable" browser command (like page reload, page back and page forward), the screen tries to automatically undo this action by relocating in the "current" screen. This behavior is in line with the "desktop application" model and can’t be avoided reliably, but may be mitigated by employing a "borderless" window (without the navigation buttons' panel.) Anyway, the usr-nav-path initialization parameter may be used to provide an external error page if the default behavior is not desired.

Note
AWF does not provide a facility for the creation of a borderless window: this must be setup manually by the user.
  1. The u-exit action is the conventional identifier for a client user requesting to exit from the current screen (usually to the "parent" one.) This is not enforced by the system.

Note
The developer is encouraged to provide a suitable exit way from any screen, and a "logout" button.

A common pattern is shown in the next example:

	@AwfHandle("u-exit")
	public void exitScreenRequest() {
		AwfCmd.ret();
	}
  1. The @AwfDestroy annotated methods are called for all the screens in the current call stack (in reverse order to their creation) when the session is destroyed (usually at logout time.) This method should be implemented when the screens have references to resources not eligible for garbage collection, like opened streams, established connections, temporary files, etc.

  2. The AwfCmd.logout() methods are used to destroy the established session and redirect the browser to an external web page. Unlike other methods, here the destination is not a logical name but an external resource. In the next example, we redirect to bad_login.html which is published in an arbitrary folder like demo-awf/src/main/webapp/exit/bad_login.html:

AwfCmd.logout("exit/bad_login.html");

Destination web page:

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
</head>
<body>
	<h3>SORRY, BAD CREDENTIALS</h3>
	<a href="../awf">CONTINUE</A>
</body>

Its no-arguments form induces a jump to the configured default "logout screen" (the exit-logout servlet initialization parameter.

Note
The total data bytes sent from the browser can’t be larger than 256 kilobytes; this limit may be changed with the max-post-len initialization parameter or per screen with the maxPostLen attribute of @AwfScreen (which overrides the former.) This is a mitigation against attacks which try to overflow the system memory.
About the "login" process

The "login" screen is special in several aspects, basically in order to avoid unauthorized access and other kinds of remote attack.

  1. In order to use the application with more than one window or tab, the corresponding login processes must be performed on each one. It is possible (though rather confusing) to login with distinct users using the same browser.

  2. The only action request supported in the login page is the login attempt; that is, the event handler implementation will not receive other user requests nor may reset the screen data. This restriction is enforced to mitigate deny-of-service attacks by unauthenticated requests.

  3. There are no constraints in the number or type of components used for the user authentication, but the user action value (data-awf attribute) for the login attempt must be u-login (Java constant Awf.LOGIN_REQUEST) ; this was shown in the previous example:

<input type="button" data-awf="u-login" value="Login">

Now, the welcome screen controller:

@AwfScreen(name = "welcome", view = "welcome.html")
public class ScrWelcome {

	AwfTextbox hora;

	@AwfHandle
	public void work() {
		String action = AwfCmd.action();
		if("to-page-1".equals(action)) {
			AwfCmd.gosub("page1");
		} else if("actx".equals(AwfCmd.arg())) hora.setText("something...");
	}
}

Here we see a new "jump" method with the AwfCmd.gosub() call, which -unlike AwfCmd.go()- does as a "returnable" jump. The destination screen (not described here) may issue a AwfCmd.ret() object in order to return to the caller screen.

Note
The sessions have a default timeout of 300 seconds (five minutes); this value may be configured with the timeout-seconds initialization parameter.
Note
By default there is no timeout control in the client: only after a user request the timeout screen is redirected into. In some cases, it is convenient to enable a "client side" timeout control, forcing the web browser to do the redirection automatically (for idle sessions.) This is enabled with the cln-timeout-ctrl initialization parameter, which must be set to "1" in this case.
Note
Unlike normal screens, the data sent by the browser from the login screen can’t be larger than 256 bytes, which is enough for a typical setting of "username" and "password"; it should be increased if the screen needs to provide more authentication information. The limit may be configured with the max-login-post-len initialization parameter. This is a mitigation against attacks which try to overflow the system memory from non authenticated origins.
The Logout screen

It is used to cancel the session and is issued:

  • When a "no arguments" AwfCmd.logout() call return is returned by the screen event handler

  • When an AwfCmd.ret() is returned and no previous screens remain in the stack

  • When the action u-logout is fired (the screen event handler is not called) as in the following example:

	<button data-awf="u-logout">Logout</button>

The logout screen may be configured by the exit-logout initialization parameter. By default the logout screen points to AwfServlet.DEFAULT_EXIT_LOGOUT = "awf", which issues the login screen, but the user is encouraged to provide an alternative; for example, configuring the initialization parameter:

AwfServlet.IP_EXIT_LOGOUT -> exit/logout.html

A static logout.html document may be configured in any directory (exit in this case) relative to the web server’s document root. Note that (unlike the go() or gosub() cases) this is not an AWF screen, so it has no associated controller nor authentication control.

Updating the current screen

When no "jump" is needed, the handler code does not need to issue any AwfCmd method; in this case AWF simply updates the screen with the changes introduced in the screen widgets (like in the hora.setText("…​") line.)

In this case, the AwfCmd.addCall(functionName) and AwfCmd.addCall(functionName, arg…​) methods allow the execution of "client calls" to be run as Javascript functions in the browser. A number of calls may be setup this way. The client side will execute the provided functions by name assuming they are of "global" kind, by calling the browser window object member.

It is very common to simply issue an alert or a confirmation dialog; in that case, the AwfCmd.alert() and AwfCmd.confirm() static methods setup such calls. Those methods will trigger the awfAlertFunct() and awfConfirmFunct() Javascript functions which may be defined in the view. If not defined, the awf.js engine will employ window.alert() and window.confirm(), resp. The developer is encouraged to provide nicer implementations for these functions with custom dialogs.

The awfAlertFunct(title) is tailored to display a modal dialog with a message which is provided as its single argument. Likewise, the awfConfirmFunct(message, actionYes, actionNo) is used to ask a confirmation related to the provided message, which should include a couple of buttons for yes/no answers. The actionYes and actionNo are AWF actions which should be sent to the server (using awfFireAction() when the corresponding buttons are pressed. Any of those actions may be null when the server does not need to be notified of such case.

The client code is in charge of closing the dialog window.

The following is an example illustrating the setup of a custom dialog using Bootstrap:

<div class="modal fade" id="cnfModal" data-bs-backdrop="static" data-bs-keyboard="false"
	tabindex="-1" aria-labelledby="cnfModalLabel" aria-hidden="true">
  <div class="modal-dialog modal-lg">
    <div class="modal-content">
      <div class="modal-header">
        <h1 class="modal-title fs-5" id="cnfModalLabel">Confirmation Request</h1>
      </div>
      <div class="modal-body"><p id="cnf-txt"></p></div>
      <div class="modal-footer">
        <div class="input-group has-validation px-3 py-2">
          <button type="button" class="btn btn-primary" id="cnf-yes" data-bs-dismiss="modal">Yes</button>
          <button type="button" class="btn btn-primary" id="cnf-no" data-bs-dismiss="modal">No</button>
		</div>
      </div>
    </div>
  </div>
</div>
<script>
let lastYes = null, lastNo = null;
document.getElementById("cnf-yes").addEventListener("click", ()=>{
	if(lastYes) awfFireAction(lastYes);
});
document.getElementById("cnf-no").addEventListener("click", ()=>{
	if(lastNo) awfFireAction(lastNo);
});
function awfConfirmFunct(text, acty, actn) {
	document.getElementById("cnf-txt").innerText = text;
	lastYes = acty; lastNo = actn;
	showModal("cnfModal");
}
</script>
<script src="webjars/bootstrap/5.3.2/js/bootstrap.min.js"></script>
The ScreenSession

This object allows the application to store information exclusive to the session. Also brings access to the servlet context and the "principal" object (identity of the user) established in the login screen.

Implementation Note

A "session store" is created for the servlet (class AwfSessionStore); that is, we ignore the servlet session supported by the servlet container. This is in order to provide independent browser’s "window sessions" or "tab sessions", which is limited by the use of a standard session cookie. This way, the user may log-in thru distinct browser windows or tabs of a single browser, and work in totally independent sessions. For example, logging out one open window or tab, will not logout from the others.

Exceptions

When the screen event handler method throws an exception, the system will log it and redirect to the configured "internal error" html page (not a screen) which is configured via the internal-error-path initialization parameter.

Note
The default value for internal-error-path is awfres/internal.html. The "awfres" path is handled by the AWF servlet and provides a trivial html document suitable for testing at developing time. The developer should override this default with a custom error page.

Views

The view attribute of the @AwfScreen annotation is used to configure the "view template" to be associated to every screen. This is handled by a "template processor" object configured with the templateProcessor attribute of the annotation.

The template processor must implement the ScreenTplProc interface. It provides the method:

public String[] process(String name, URL resource) throws IOException;

The name is the "logical" screen name. This method must return two strings corresponding to two consecutive sections of a HTML document; between these sections the AWF servlet inserts Javascript code which interacts with the server.

If the template processor is not configured, AWF provides a default implementation (class DefScreenTplProc) which expects an HTML document containing the literal comment:

<!--awf-jip-->

which marks the point of insertion of AWF Javascript. If no such comment is found, the insertion point is just before the closing body element: </body> or </html> (in lower case; if both are missing, an exception is thrown.)

The default implementation also looks in the template for lines with the pattern:

<!--awf-include(filename)-->

which signals the inclusion of the provided file name (using the same location as the templates.)

More on the template processor

The template processor loads the resource and produces two string components for the insertion (between them) of the awf.js script. Also, it may transform the view in any other way; for example, the default template provides "inclusion" syntax disguised as HTML comments.

Note that the template scan happens once on the servlet init phase, but in "development mode" the WebPage instances are recreated each request in order to allow for fast experimentation with updated (by the developer) views.

Manual action invocation

From Javascript, it is possible to call the server by issuing the awfFireAction() global function, providing the "user action". For example, to allow the login action by pressing [Enter] (besides the usual button), the following code may be added to the template:

<script>
	document.addEventListener("keydown", function(event) {
		if (event.keyCode === 13) {
			event.preventDefault();
			awfFireAction('u-login');
		}
	});
</script>

Screen widgets

The event handler allows the access to the screen widgets. Those widgets may be read by the screen code and written in order to update the screen view. Besides the "actionable" buttons previously described, we support a number of widgets usually associated to HTML forms.

Note
No HTML <form> should be added to the screen template. The widgets' information is dynamically submitted with an Ajax POST request.
Textual input fields

They refer to the HTML input elements with a type of text and similar like email, etc. See https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input for more information.

Obvious non textual types are button and file.

Those elements are mapped to the AwfTextbox Java class in the server, and the user may change its String value.

Also, any HTML textarea element with the data-awf attribute is mapped a AwfTextbox object.

Selectors/Comboboxes

The HTML <select> elements containing the data-awf attribute are mapped in the server to a AwfSelect Java object, which allows the extraction and change of the contained options (modeled by the AwfOption class.)

There is a multiple read-only attribute (set in the HTML view) which helps determine whether the selector admits multiple selection.

Checkboxes

The HTML <input type="checkbox"> elements are mapped to Java objects of AwfCheckbox, reflecting the "checked" state.

Radio groups

A number of same-named HTML <input type="radio"> elements is considered a "radio group", and is mapped to an AwfRadioGroup object.

Images

The <img> elements marked with data-awf will be mapped into an AwfImage object, allowing the dynamic change of their source path.

File upload

The <input type="file"> element marked with data-awf and data-awf-upload-action attributes is mapped to an AwfFile object, allowing the retrieval of the file’s binary contents. The data-awf-upload-action signals the action which triggers the upload; this is necessary since there could be several buttons in a screen but only one is in charge of the submission of the files.

Note
If the data-awf-upload-action is missing, the uploads will not happen. A warning will be issued into the Javascript console in this case.

Multiple file selection is supported by the usual procedure of adding the multiple option to the HTML input element.

The max-upload-file-len defines the maximum bytes per uploaded file and is controlled in the client (when the file is selected.) This application parameter may be overrided by the maxUploadFileLen attribute of @AwfScreen.

Also, the total POST contains the encoded sum of the files' contents, and must be less than the maximum POST request size of the screen.

Note
These widgets are not intended for uploading very big files, since all the files' contents are loaded into the web browser memory before the submission, and encoded as a Json string. A streaming not-json-encoded solution is in order, but is outside the AWF capabilities.
Note
The Json transfers the file contents encoded in base 64, so the effective transfer size is about 4/3 with respect to the source. The max-post-len attribute should be set at least to the highest sum of effective transfer sizes per application screen, in order to allow the uploads.
Note
The servlet container may impose additional limits. For example, Apache Tomcat has a maxPostSize parameter, but (confusingly) it does not have any effect in AWF since the former only apply for the standard form "application/x-www-form-urlencoded" content type, but the later uses "application/json", so in practice no such limit exists in that servlet container.
Labels

HTML elements without children nodes and marked with data-awf will be mapped to a Java AwfLabel object, which allows to dynamically change the contained text. Usual elements are span and p; dynamic "clickable" content may be generated with a elements (with href="#".)

Html

Any remaining HTML elements containing children nodes and marked with data-awf will be mapped to a Java AwfHtml object. This is used essentially to change its attributes; for example, the display of an <div data-awf="xdiv"> element may switched with xdiv.setAttribute("style", "display: none") or xdiv.setAttribute("style", "display: block").

File download

Any action may be programmed to request a "file download". The action may be triggered from a button or an hyperlink as previously described.

The Java handler for the action must return a "download" response with the AwfCmd.download() methods providing a disk file, a byte array or a text string.

Table widgets

For a related set of widgets, usually associated to an HTML table or a number of similar "rows", AWF provides the AwfTable class. The programming model assume some "tabular" HTML markup already exists in the screen template (usually a table element), with elements having a data-awf attribute with the patterns name#row or name#row#column. The former is an abbreviation for name#row#1.

The row part must be an integer starting with 1; the column part may be any value, usually reflecting the underlying columnar concept.

The AwfTable may be used as an "indexed" container of widgets, which are retrieved by the getWidget(row,column) method.

Since it is usual to work with textual tabular data, the getTextualValue() and setTextualValue() helper methods are provided; also, the displayTextualValues() helper allow the immediate setting of textual values from Java lists of Strings.

Pagination

The AwfTable may be used for automating the display of paginated tabular textual data. The data is provided to the widget with the setPaginatedTextualData() and setPaginatedDataSource() methods, which provide the data to be shown page by page.

  1. setPaginatedTextualData() expects an immutable list of rows; this is usually a short one (in terms of memory), for example, a list with the items of a sale order interactively created by the user

  2. setPaginatedDataSource() expects a function to extract lists of rows for a page (specified as a range of rows.) The function, for example, may ask a database for rows in a result set

    • These methods are usually called the first time the screen is executed, but it is safe to call them again later, effectively replacing the associated data

    • The pagination code assumes the provided data is immutable; if it changes while in use, the results are undefined: if needed, a new method call should be issued to reset the data

    • For any widget only one of the methods may be called (several times.) Each one setups an operation mode for the widget which must not be further changed

    • The user may define action buttons for page navigation associated with the following data-awf attributes, where name is the widget name:

  3. name#@N → next page

  4. name#@P → previous page

  5. name#@F → first page

  6. name#@L → last page

  7. name#@J → jump to page

  8. name#@D → the current page or the destination page to jump

Actions in Table Widgets

It is usual to generate actions from clickable widgets in a widget set. Using the described numbering convention, AWF provides some facilities in order to handle these events. For example, the actions for a clickable widget name#col#row are named just name, and a TableAction property is available via the AwfCmd.tableAction() method in order to extract the clicked row and column.

The following example shows a table with three rows supporting the pagination of its (potentially larger) contents, and clickable hyperlinks:

		<table class="table table-striped table-sm">
          <thead>
            <tr>
              <th scope="col">&nbsp;</th>
              <th scope="col">#</th>
              <th scope="col">Code</th>
              <th scope="col">Description</th>
            </tr>
          </thead>
          <tbody>
            <tr>
              <td>&nbsp;</td>
              <td><span data-awf='u-itemlist#1#num'>&nbsp;</span></td>
              <td><a href="#" data-awf='u-itemlist#1#code'></a></td>
              <td><a href="#" data-awf='u-itemlist#1#desc'></a></td>
            </tr>
            <tr>
              <td>&nbsp;</td>
              <td><span data-awf='u-itemlist#2#num'>&nbsp;</span></td>
              <td><a href="#" data-awf='u-itemlist#2#code'></a></td>
              <td><a href="#" data-awf='u-itemlist#2#desc'></a></td>
            </tr>
            <tr>
              <td>&nbsp;</td>
              <td><span data-awf='u-itemlist#3#num'>&nbsp;</span></td>
              <td><a href="#" data-awf='u-itemlist#3#code'></a></td>
              <td><a href="#" data-awf='u-itemlist#3#desc'></a></td>
            </tr>
		</table>
		<div class="col input-group">
			<button data-awf='u-itemlist#@F' class="btn btn-primary">Start</button>
			<button data-awf='u-itemlist#@P' class="btn btn-primary">Prev</button>
			<button data-awf='u-itemlist#@N' class="btn btn-primary">Next</button>
			<button data-awf='u-itemlist#@L' class="btn btn-primary">End</button>
			<input data-awf='u-itemlist#@D' class="form-control" type='number'>
			<button data-awf='u-itemlist#@J' class="btn btn-primary">Go</button>
		</div>
Note
Though AWF is not oriented to dynamic HTML markup, a common pattern is the dynamic generation of the rows for a table. This pattern must be implemented by the developer in Javascript code. But there is a catch: the framework captures the widget set components once in the first request, and later invocations will not recognize further additions. In order to force the capture in every invocation, a workaround consists in prefixing the widget set name with the text dyn-. Again, this pattern is discouraged and is inefficient in AWF.

HTML Attributes and CSS classes

The setAttribute(name, value) method may be used to specify HTML attributes (like style.) Also, the addCssClass(className) and delCssClass(className) allow adding and removing CSS classes in the element.

Those attributes are applied in the HTML document, but if overwritten by user Javascript code, the updated values are not reflected in the server.

Automatic login

This facility is oriented toward improving the development ergonomics: when developing a screen, in order to accelerate the testing iterations it is convenient to automate the steps needed to access the target screen, starting from the login. The automatic login mode allows the automated registry of the session and jump to the desired screen as soon as the user reloads the AWF servlet (usually just pressing [F5] in the web browser.) Two things are needed:

  1. Configure the AwfServlet.AUTO_LOGIN to 1

  2. Add a handler for the Awf.AUTO_LOGIN action (auto-login) in the login page controller as in the following example, providing the desired principal and destination screen:

	@AwfHandle(Awf.AUTO_LOGIN)
	public void autologin() {
		AwfCmd.auth("admin", "inventory");
	}

Bean injection

AWF provides a very-lightweight alternative to a full dependency injection library/framework, providing easy management of singleton bean instances. Its usage is totally optional and is activated only if the bean-package servlet initialization parameter is configured.

Implementation Note

The "bean container" is provided using the facilities of the Reflections library (class BeanStore.) This is used to support the "screen beans" which are page controllers annotated with @AwfScreen, and arbitrary singleton beans which are classes annotated with @AwfBean, as a really lightweight alternative to a full bean container. Eventually, the bean container facility could be externalized as an independent project.

The managed beans' constructors may be without parameters or with ServletConfig and ServletContext typed ones. The beans package location must be configured with the bean-package servlet initialization parameter.

These beans may have other beans as dependent fields by signaling them with the same @AwfBean annotation (but at field level.) The screen controllers (@AwfScreen annotated classes) may also have @AwfBean dependent fields. Note that the screen instances are no singletons, unlike the injected beans.

The @AwfBean annotation allows an explicit naming of the bean with the name attribute; else, it is assumed that only a single bean exists for its class type (a fictional name is derived from the class name.) Such beans here are known as "one in its class".

It is possible to inject bean implementations through interface typed fields as in most DI frameworks. This is a rather slow process as compared with field injection by a named bean, specially on screen controller classes. Currently, AWF does not check if more than one bean implements the specified interface, so which concrete bean is injected is undefined when multiple beans implement it.

Inside any @AwfBean marked bean class, a number of static or instance level methods may also be annotated with @AwfBean, which makes them "factory" methods. Such methods will be executed by AWF exactly once in order to create additional singleton beans. This is useful when we do not have access to the source code of a class (i.e. not able to annotate its type) from which a bean instance needs to be created.

After all the beans are instanciated, the field injections proceed, and the initializer methods are called if defined. The initializer methods are designated by the @AwfInit annotation; these may have the same parameters as the constructor and also the defined beans which are "one in its class". Unlike the constructor, several initializer methods may be defined and all will be executed (though it is a dubious programming pattern.)

The screen controllers also may have initialization methods which will be called only once after instance construction, with the same semantics as singleton beans. In this case, the methods may have the associated ScreenSession as a parameter.

Note
The order of construction and initialization is determined by the order attribute which defaults to Integer.MAX_VALUE (lower values start first.) The beans created by the class level annotation or static factory methods are all instanciated before the beans created by instance-level factory methods.
Note
All the singleton beans are created and initialized once when the AWF servlet is initialized. At that time no HTTP requests are processed so no screen controller exists yet.
Note
The reverse of @AwfInit is @AwfDestroy, which is called when a screen session is dropped and (for the singleton beans) at the AWF servlet destruction time.

Below there is a "login" screen employing a fictitious LoginScreenAuthenticator bean:

@AwfScreen(name = "login", view = "login.html")
public class ScrLogin {

	private static final Logger logger = LoggerFactory.getLogger(ScrLogin.class);

	AwfTextbox name;
	AwfTextbox pass;
	@AwfBean
	LoginScreenAuthenticator authenticator;

	@AwfHandle("u-login")
	public void loginMethod() {
		if(authenticator.auth(name.getText(), pass.getText())) {
			logger.info("Successful login for {}", name.getText());
			AwfCmd.auth(name.getText(), "welcome");
		} else {
			logger.warn("Bad login - exiting to bad_login.html");
			AwfCmd.logout("exit/bad_login.html");
		}
	}
}

Validation

AWF provides an optional facility to streamline the validation of widgets' input. Any non-login screen may define per-action static methods which fill a ActionValidations parameter. In the following example, three validators are defined for the execution of the act-calc action; in this case, the three mentioned widgets have each one validator, but zero o more validators are acceptable per widget:

	@AwfValidation("act-calc")
	public static void calcVals(ActionValidations vals) {
		// widget, regular expression, error message
		vals.add("arg1", "[0-9]+", "The first argument must be numeric");
		vals.add("arg2", "[0-9]+", "The second argument must be numeric");
		vals.add("op", "\\+|-|\\*|/", "The selected operation is invalid");
	}

Such object is used to declare a number of validators to be applied to the textbox, select and radiogroup widgets in the client (for best performance) and optionally also in the server (for best security.) Each validation consists of the widget identifier, a regular expression, and a report message to be shown on action trigger when the widget value is not acceptable. There are several signatures of add() in ActionValidations for further control of the regular expression matching.

In the client side there is a "default" validation error handler which just alerts on the first validation failure:

let awfValDefault = (valFailures)=>{
	if(valFailures.length > 0) {
		awfAlertFunct("There are validation errors: " + valFailures[0].failMsg);
		return false;
	}
	return true;
};

The developer may develop an overriding function named awfValidationFunct which implements a similar logic: returning true does mean that the request may be carried on, else some other action should be taken (like showing an alert), and the request is cancelled. This function receives an array of objects containing the following fields:

  • widget: the culprit widget identifier

  • failMsg: the "failure" message to inform the user about the unsatisfied validator

  • actual: the actual value in the widget (the user input)

It is up to the developer how to handle the validation failures; for example, some applications will present a dialog with the list of the failures; in other cases the web page gets warning annotations besides each failed widget, and so on.

Note
When server validation is enabled (by providing a Java Pattern instance to ActionValidations) then it is expected that its behavior replicates the Javascript version. If the server validation fails, then it is either because of an error in the Javascript engine (possibly in the validation failures' handler), or a hacking attempt (which maliciously overrides the validations in the client); in any case of server validation failure the user is redirected to the internal error page.
Note
The login screen currently does not support validators for a technical limitation which could be solved in a future release. The validators are currently sent bundled with the widget’s initial values, which are calculated only for authenticated sessions (POST requests) to avoid some "deny of service" scenarios: the login screen gets an empty (fixed) bundle which does not require recalculation for each non authenticated request. But since validators are defined in static methods, such login "constant bundle" could be endowed with them.

The post-ready function

When a screen is loaded (the HTML document is fetched) the AWF Javascript engine is executed as a DOMContentLoaded event handler. If the developer defines a function named awfPostReadyFunct(), it is executed at the end of the mentioned event handler. It may be leveraged to extend the AWF behavior in a consistent way.

Improving the load behavior

When a screen is loaded the AWF Javascript engine notifies the server and potentially fetches the initial state of some widgets. This time lapse has an arguably awkward behavior since the browser already displayed the HTML contents but a "final version" is yet waiting for the server response.

A solution is to hide the screen and wait for the initial request to be answered. The following examples were adapted from https://stackoverflow.com/questions/9550760 :

Example: styling the html element

This solution hides the document via CSS and provides a function to show it later:

<!DOCTYPE html>
<html style="visibility:hidden" lang="en">
<head>...</head>
<body>
...
<script>
function htmlEnableView() {
	document.getElementsByTagName("html")[0].style.visibility = "visible";
}
</script>
</body>

The Java controller provides the needed call to be triggered when the document is shown:

	@AwfHandle(Awf.ACTION_JUMP)
	public void showJump() { AwfCmd.addCall("htmlEnableView"); }
Example: adding a cover to the screen

This solution adds a div element covering the screen which is later removed using the same Java code as in the previous case. The example div shows a black background with a centered image (for better results, replace with a "clock" animation):

<head>
    <style>
	#cover {
		position: fixed; height: 100%; width: 100%; top:0; left: 0;
		background: #000000; background-repeat: no-repeat;
		background-image:url('media/logo.png');
		background-position: center; z-index:9999;
	}
	</style>
</head>
<body class="bg-light">
<div id="cover"></div>
... the normal content ...
<script>
function htmlEnableView() {
	document.getElementById("cover").remove();
}
</script>
</body>

The operation of the AWF servlet

The rest of the document deals with the internals of AWF, and should not be needed for using it.

Logic for doGet

Cases upon HttpServletRequest.getServletPath():

Login

When the path is /awf or "empty", the "login" WebPage is built and sent to the browser without the "initial server call" instruction.

Static resource

When the path is /awfres, the corresponding resource is loaded from the classpath; currently we define the resources:

  • /awf.js

  • /internal.html

  • /timeout.html

The last two may be configured with the initialization parameters timeout-path and internal-error-path.

Other path requests are considered errors. Any other interaction is carried on with the POST method.

Logic for doPost

There are two kinds of POST requests: an XDR fetch when the client provides a user request and widget updated information. When the client gets the response, it may be instructed to update the widgets and do nothing (the user got its results), or do a "page jump"; in this last case the client will issue a plain text/html POST request (from a form created on the fly) with the minimal authentication information. It could have been a GET, but we opted for a POST to avoid url parameters in the browser tab. The following explanation begins with the later case:

a) If the HTTP Accept header contains text/html, then it is assumed an HTML page is requested. On error the request is redirected to the internal error page.

The AWF_SESSION cookie must exist and is used to extract a mandatory existing session. Also, the POST parameters awfcrsf (CRSF protection) and awfseqcode (current page sequence code) are captured and validated with the current session.

Note that if awfseqcode is invalid, if usually means a browser refresh attempt, so it is handled according to the usr-nav-path initialization parameter.

Then the handleFormPost() method is executed. This method does create a new page sequence code and set into the session. If the screen is shown for first time, then the "need initial call" boolean flag is set, which instructs the awf.js script to make it.

b) Otherwise the request is assumed to be Json text from an Ajax request. On error, an "Exit redirect" order is sent to the awf.js script in order to go to the internal error page.

The Json text is parsed with Jackson as a Java Map<String,Object>; this map is used to identify the browser provided widgets, a Java Map<String,ScreenWidget> is produced by the ScreenExtractor.extract() method.

As in the previous case, the awfcrsf and awfseqcode Json attributes are extracted and used to get the associated session (if any.) Also, the AWF_ACTION Json attribute is captured for special processing:

If contains u-logout, then a redirection to the logout page is in order.

If contains u-login, then the login screen controller is called.

Else, the awfcrsf and awfseqcode attributes are processed (like in the previous case.)

If all right, the controller object is extracted from the session and the doScreenFunction() is executed, which calls the user screen controller method, and acts upon the response.

Again: the user screen method is called only for doPost when an Ajax Json request is sent from the browser.

The doScreenFunction() method

a) The screenData widget map is iterated and the contained widgets are marked as "unmodified" (ScreenWidget.markUnmodified() method.) This is used to avoid re-sending unmodified widgets to the browser in the response, to reduce network traffic usage and re-draw time in the browser.

b) The injectWidgets() method is in charge of injecting the declared widgets of the user provided screen. This is made with the help of a map containing the java.lang.reflect.Field objects which are captured via reflection while scanning the widgets in the WebPage.scanPages() method.

The AwfTable pseudo widgets are "heavy", since they need to search in all the widgets sent by the browser looking for the prefix name#.

The rest of the widgets are updated only if they come from the browser.

c) Next comes the call to the screen controller method using the current session, or the "null session" for the login page. The return value must be not null or a NPE is thrown.

Any exception is catched and logged. The action taken depends on the onException attribute of the AwfScreen annotation (defaults to the error page.)

The following is the processing of the user code response (ScreenAction interface.)

d) If the response is a ScreenActionExit object, then call the sendExitRedirect() method which drops the session and sends an AWF_EXIT command to the browser which in turn, forces a redirection to the logout page (defaults to awf.)

e) Else, we assume the response will contain new values for the widgets in the browser, so we prepare a map containing the currently "modified" widgets

f) Process ScreenActionUpdate by sending the widgets; optionally add the function calls if the addCall() method was used, together with the AWF_CALLER command.

g) Process ScreenActionAuthenticated; this object already was created by user code, which is in charge of validating the remote user credentials.

A new session is created, storing the principal attribute.

The ScreenActionAuthenticated is converted into a ScreenActionGoto for further processing.

i ) In this point we check that a session is active, else do a sendExitRedirect(). All the previous actions could happen for a session-less request (i.e. the login request.) For example, an implementation may provide a feedback message for a bad login attempt.

j) The ScreenActionGoto, ScreenActionGosub, ScreenActionReturn and ScreenActionDownload responses are processed, dealing with the session stack. These methods use the sendJump() method which provides the jump command (AWF_JUMP command), the current CRSF token, and the current sequence to the browser.

The buildHtml() method

The buildHtml() method is in charge of producing an actual HTML page response. It consists of:

  1. The "before" part of the template

  2. An script section containing:

    1. The page name for debugging purposes awfScrLog

    2. Boolean flag for development mode awfDevMode

    3. Security label for CRSF awfCrsfCode

    4. Control for sequence of user navigation awfSeqCode

    5. Configuration for client-side timeout timer awfClnToutMs

    6. Configuration for client-side timeout destination awfToutRedirectUrl

    7. Boolean flag whether exit on browser refresh awfExitOnRefresh

    8. Limit for file uploads awfMaxUploadFileLen

    9. Function name for messages awfMsgFn defaulting to alert

    10. Boolean flag whether initial call is needed awfInitialCall

    11. A base64-encoded map containing the updated screen widget values awfIniScr

  3. Script element reference for the load of awfres/awf.js

  4. The "after" part of the template

The widgets' lifecycle

Each screen (controller + template view) is stored internally in a WebPage instance. The servlet has a map containing these WebPage instances indexed by the screen logical name.

In the server, the widgets are objects implementing ScreenWidget (from the user framework point of view), but also extending from the abstract class BaseScreenWidget in order to support more features while not complicating the user API: this later class only has package-level access methods which are called by the AwfServlet code.

Those widgets are collected in a Map<String,ScreenWidget>, and it is provided to the browser as a Json string (together with the CRSF token and synchronization sequence.)

Note that this transfer only happens as a response of a browser initiated HTTP request, so we are dealing here with the "response to the browser".

Now we’ll see the browser point of view.

Login

The login page is loaded by a GET request into the AWF servlet. At first, the login page must provide HTML elements which correspond to the widgets user for authentication (like username and password fields.) In the server there is no session, and the browser (with the awf.js code) builds a Json request upon user authentication request (i.e. click on sign-in button.)

The data-awf marked elements are sent in this Json string to the server to be converted into ScreenWidget objects using code inside the ScreenExtractor class. Those objects are injected into the corresponding @AwfScreen user-provided object before the screen controller method execution, in order to make available the widget contents.

The BaseScreenWiget has a flag for registering which widgets are modified by user code (from the screen controller method), and those "touched" widgets are sent to the browser for the corresponding redraw.

Instead, if the authentication is successful, a "page jump" command is expected to be returned to the browser (without further widget information.) On "page jump" command, the browser will execute a POST request to the servlet, for obtaining the new HTML document (the target page is already set in the associated server session, so the browser does not specify the destination page.)

Screen Load - first time

Except from the login screen, the screens are loaded by POST requests. The first time the screen is invoked, a new @AwfScreen object instance is constructed and is associated to the server session. The response correspond to a HTML document built from the associated screen template. At this time, no ScreenWidget objects exist.

When the document is loaded in the browser, the awf.js script issues a "initial request" containing the data-awf marked elements as a Json string, so the servlet has the opportunity of extracting the corresponding widgets and issue a first call to the screen controller method. On return, the screen is marked as "initialized".

Note that the browser here issues the request with the pseudo-action awf-initial-call, which is converted into the real programmed action stored in the server session.

As in the previous case, only the "touched" widgets are returned to the browser for redraw.

Screen Load - later times

The user may jump from a screen but return later (after a ScreenActionReturn response.) In this case, the screen widgets are already loaded in the server (thru the screen instance in the screen stack of the session.)

Now, the "touched" widget contents will be included in the HTML document as a Json string, and a flag will be set to avoid the previously described "initial request". This is made in order to save an extra unneeded browser request and improve the application responsiveness.

The Javascript engine

awf.js drives the browser interactions. On load, it scans the widgets using document.querySelectorAll("[data-awf]"). All the interesting HTML elements are stored in the awfElementList array, some as plain DOM objects, others (like a radio group) as a new "internal widget" object.

Those elements are repainted using awfRepaint(), employing the initial values if provided in the awfIniScr object (coming from the servlet.) That routine is called later for each user interaction.

The buttons have their "click" event pointing to the awfFireAction() function, which builds a new object containing the associated widgets which were changed by the browser’s user. Those are sent to the server as a Json request.

Note about the tests

The HtmlUnitTest is the main test to execute and prove the widgets using the HtmlUnit Javascript engine. Uses the HtmlUnitUnit to streamline the coding. The BeanTracker is used to count the registered bean method calls.

The ManualTest just executes the test application using embedded Tomcat and the client user must employ a web browser. Uses the "EmbeddedTomcat" class. The DelegateToSlf4jLogger is used to reconfigure Tomcat logging to Slf4j.

Reference

Table 1. AWF Servlet Initialization Parameters
Mandatory Parameter Default Description

No

development

0

If '1' emit debug information and disable template caching

No

auto-login

0

If '1' enable automatic login mode

No

exit-logout

awf

External web page after session logout

No

view-resource-prefix

/WEB-INF/template/

Prefix for the screen template path

Yes

screen-package

Package to scan for screen controller classes*

No

bean-package

Package to scan for singleton Bean classes*

No

cln-msg-fn

alert

Javascript function for displaying client side message

No

internal-error-path

awfres/internal.html

Internal error page

No

timeout-path

awfres/timeout.html

Timeout error page

No

usr-nav-path

*

The error page to land on browser navigation action, or reload if asterisk

No

exit-on-refresh

0

If '1' exit to login on browser refresh action

No

timeout-seconds

300

Session timeout in seconds (server controlled)

No

cln-timeout-ctrl

0

If '1', activate client side session timeout control

No

max-post-len

262144

Maximum bytes in browser post requests (server controlled)

No

max-login-post-len

256

Maximum bytes in browser login post requests (server controlled)

No

max-upload-file-len

65536

Maximum bytes for each uploaded file (client controlled)

No

screen-stack-capacity

10

Capacity of screen stack for GOSUB calls

* More than one package may be specified by separating with commas