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>
-
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 thejavax.
package, and replace theawf
dependency forawf-jee
. -
The servlet named "awf" is configured
-
The
screen-package
initialization parameter is provided with a package name for class scanning as we’ll see in the next section -
The optional
development
initialization parameter set to1
enables debug messages viaslf4j
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 -
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");
}
}
-
The "logical" screen name is defined in the
@AwfScreen
annotation’sname
attribute. The view file name is defined with theview
attribute. The widgets are fields whose name matches the view’sdata-awf
attribute previously described; optionally the@AwfWidget
field annotation allows such fields to point to an arbitrary widget in the view -
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. -
The
AwfCmd
has static methods which provide useful information regarding the current request. It also have methods with allow to signal the request outcome, likeauth
andlogout
in the example. -
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 formval1:val2
then theval1
is theaction
String parameter andval2
is thearg
extra argument to be extracted withAwfCmd.arg()
. -
The "user requests" are issued from any
button
orinput type='button'
HTML elements with thedata-awf
attribute. Also, from "clickable" HTML elements having adata-awf
attribute which starts with the prefixu-
(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. -
The
AwfCmd
static methods are used to signal the desired outcome for the handler method. Thego()/gosub()
methods instruct AWF for "jumping" to a "destination" screen specifying its logical name, optionally specifying the destination screen controller’sarg
parameter. Theauth()
methods are valid only in thelogin
screen to signal a successful authentication and setup a new screen session. -
After a jump (
go()
,gosub()
andret()
requests) the destination page fetched and its controller receives aAwf.ACTION_JUMP="jump"
action. -
The
AwfCmd.previousPage()
method provides the name of the previously displayed screen. -
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.)
-
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. |
-
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();
}
-
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. -
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 tobad_login.html
which is published in an arbitrary folder likedemo-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.
|
The "login" screen is special in several aspects, basically in order to avoid unauthorized access and other kinds of remote attack.
-
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.
-
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.
-
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 beu-login
(Java constantAwf.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.
|
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.
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>
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.
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.)
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.
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.
|
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.
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.
The HTML <input type="checkbox">
elements are mapped to Java objects
of AwfCheckbox
, reflecting the "checked" state.
A number of same-named HTML <input type="radio">
elements is considered
a "radio group", and is mapped to an AwfRadioGroup
object.
The <img>
elements marked with data-awf
will be mapped
into an AwfImage
object, allowing the dynamic change of
their source path.
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.
|
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="#"
.)
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")
.
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.
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.
-
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 -
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, wherename
is the widget name:
-
-
name#@N
→ next page -
name#@P
→ previous page -
name#@F
→ first page -
name#@L
→ last page -
name#@J
→ jump to page -
name#@D
→ the current page or the destination page to jump
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"> </th>
<th scope="col">#</th>
<th scope="col">Code</th>
<th scope="col">Description</th>
</tr>
</thead>
<tbody>
<tr>
<td> </td>
<td><span data-awf='u-itemlist#1#num'> </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> </td>
<td><span data-awf='u-itemlist#2#num'> </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> </td>
<td><span data-awf='u-itemlist#3#num'> </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:
-
Configure the
AwfServlet.AUTO_LOGIN
to1
-
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.
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 :
html
elementThis 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"); }
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()
:
When the path is /awf
or "empty", the "login" WebPage
is built and sent to the
browser without the "initial server call" instruction.
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.
doScreenFunction()
methoda) 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 is in charge of producing an actual HTML page
response. It consists of:
-
The "before" part of the template
-
An script section containing:
-
The page name for debugging purposes
awfScrLog
-
Boolean flag for development mode
awfDevMode
-
Security label for CRSF
awfCrsfCode
-
Control for sequence of user navigation
awfSeqCode
-
Configuration for client-side timeout timer
awfClnToutMs
-
Configuration for client-side timeout destination
awfToutRedirectUrl
-
Boolean flag whether exit on browser refresh
awfExitOnRefresh
-
Limit for file uploads
awfMaxUploadFileLen
-
Function name for messages
awfMsgFn
defaulting toalert
-
Boolean flag whether initial call is needed
awfInitialCall
-
A base64-encoded map containing the updated screen widget values
awfIniScr
-
-
Script element reference for the load of
awfres/awf.js
-
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.
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.)
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.
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
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