This document will describe the process of painting a web page using Aspire/J2EE. The document will start with a simple web page and proceeds to present web pages of increased complexity. I will talk about property files, aspire tags among other things
Read the 30 minute guide available in the howto section. The 30 minute guide has introduced the reader to the following
1. Download and Install tomcat 2. Download and install aspire 3. Point tomcat to aspire template 4. Define a sample database 5. Create a simple page definition 6. Create a simple html page 7. Retrieve the html page through aspire
If you had installed and configured Aspire with Tomcat you will have a web application that is ready to run. A single tomcat can run multiple web applications on the same port. For example on "machine1:port" (localhost:8080) Tomcat can run a number of applications. These applications are identified with what is called an application prefix. For example in the URL "http://localhost:8080/aspire", The word "aspire" is identifying an application. For instance "http://localhost:8080/app2" is pointing to an application called "app2" on port 8080. As per the instructions in the 30 minute guide our focus here is the web application called "aspire" identified by "http://localhost:8080/aspire".
When you fire off the above url, Tomcat will automatically load a file called "index.html" that is kept in the directory identified by "aspire". This web application prefix to the physical application directory mapping is done in the tomcat configuration file called server.xml.
Assume that in a sub-directory called "apptemplate\mydir" there is an html file called "plain-page.html". You can retrieve this page as following
http://localhost:8080/mydir/plain-page.html
This will load the plain-page.html. This page is displayed directly by tomcat. Aspire does not play any role in the display of this page. This html page is called "plain" because it does not have any dynamic content. The page is not a jsp page, the page does not have any aspire tags, etc.
These kinds of static pages are useful for testing or providing static links to other parts of the web application. These pages does not go through the Aspire security model.
Let us raise the bar a little and let us retrieve a dynamic html page that can have a few tags. Let us call this dynamic-page1.html. Also assume that this is available in "apptemplate\mydir".
To retrieve this page you need the following three lines in a properties file
# 0). Create a URL name for your dynamic page MyPageURL=aspire:\\mydir\\dynamic-page1.html # 1). Identify a data definition for your page MyPageURL.formhandlerName=MyPageDataDef #Identify a pre-built java part that knows how to get the data for that definition request.MyPageDataDef.form_handler.class_request.className=com.ai.htmlgen.DBHashTableFormHandler1
By removing the comments from the above your definition will look like
MyPageURL=aspire:\\mydir\\dynamic-page1.html MyPageURL.formhandlerName=MyPageDataDef request.MyPageDataDef.form_handler.class_request.className=com.ai.htmlgen.DBHashTableFormHandler1These three lines will allow you retrieve the dynamic page. Here is how you retrieve it from a browser
http://localhost:8080/servlet/DisplayServlet?url=MyPageURL
Let us now consider what is in the "dynamic-page1.html". I am excluding html tags in this file because of editing reasons.
Sample html page Demonstrates simple tagsYou will see this page as it is. Because Aspire has processed the page and wrote it back as it is as it does not contain any dynamic content so far. Let us introduce a dynamic element in the page.
Sample html page Demonstrates simple tags {{arg1}} is my nameThe dynamic element in this page is identified between {{ and }}. In this example the dynamic element is "arg1". Now if you fire off the above url you will see
Sample html page Demonstrates simple tags is my nameYou will see arg1 disappear from the page. This is because there is no value for arg1 available on the server side to Aspire. Let us make this available using the following URL
http://localhost:8080/servlet/DisplayServlet?url=MyPageURL&arg1=satyaNow you will see the html page displayed as follows
Sample html page Demonstrates simple tags satya is my name
Because the above page is obtained through Aspire, Aspire uses varieties of data sources to look for the specified element "arg1". These sources include
You have already seen how URL arguments are used as a datasource. Let us see how we can extend the html page to demonstrate an argument substitution from a properties file
Sample html page Demonstrates simple tags {{arg1}} is my name {{arg2}} is my occupation
We know that "arg1" came from the URL. Now to make "arg2" available, make an entry like the following in one of your properties files
arg2=Software
Now when you fire off http://localhost:8080/servlet/DisplayServlet?url=MyPageURL&arg1=satya you will see the html page turn to
Sample html page Demonstrates simple tags satya is my name Software is my occupation
The URL arguments are also called "request" level variables. Because these variables exist for the life of a single web page request. The variables like "arg2" kept in the properties files are called applciation level variables. Because these variables do not change for the life of an application. Another type of variable is called a session variable. These variables exist for the amount of time a user logs in and uses a website. Sessions are slightly advanced for this document. So I will not discuss session variables in detail here. Before explaining how we can substitute arguments from a database let me take a detour and revisit the three lines in the properties file that allowed us to retrieve this dynamic page.
How does Aspire know to search these datasources to satisfy these dynamic arguments. The java class responsible for doing this search is "com.ai.htmlgen.DBHashTableFormHandler1". This is the class that is mentioned in the third line. This class implements an interface called "IFormHandler". This interface answers questions like "Given a key called 'arg1', give me back its value".
The above class is one implementation. If you were to write your own java class then you can interprest "arg1" as you choose. But the default implementation will search the URL arguments, followed by the properties files.
# 1). Create a URL name for your dynamic page MyPageURL=aspire:\\mydir\\dynamic-page1.html # 2). Identify a data definition for your page MyPageURL.formhandlerName=MyPageDataDef # 3). Identify a pre-built java part that knows how to get the data for that definition request.MyPageDataDef.form_handler.class_request.className=com.ai.htmlgen.DBHashTableFormHandler1
The first line tells Aspire what html file to load in response to an incoming url called "MyPageURL". The second line tells where to find the data for the url "MyPageURL" by looking for a key "MyPageURL.formHandlerName". This key in the exampel is pointing to "MyPageDataDef". Line three specifies "MyPageDataDef" pointing to a java class that implements IFormHandler.
These lines typically do not change. So it is largely a cut/paste exercise as you define new pages to aspire. This basic definition gets increasingly sophisticated as you start gathering data declratively from multiple sources with a variety of options.
Let us modify the html page one more time so that we can introduce some arguments from a database
Sample html page Demonstrates simple tags {{arg1}} is my name {{arg2}} is my occupation {{arg3}} is my preferred language {{arg4}} is another language I work in
Our goal is to get "arg3" and "arg4" from a database table. To make this happen we need to modify the properties file as follows
# Basic page definition MyPageURL=aspire:\\mydir\\dynamic-page1.html MyPageURL.formhandlerName=MyPageDataDef request.MyPageDataDef.form_handler.class_request.className=com.ai.htmlgen.DBHashTableFormHandler1 # Maindata request (Key/value pairs outside of loops) request.MyPageDataDef.maindatarequest.classname=com.ai.db.DBRequestExecutor2 request.MyPageDataDef.maindatarequest.db=request.MyPageDataDef.maindatarequest.stmt=\ select preferred_language as arg3 \ ,alternate_language as arg4 \ from languages_table \ where coder="satya"
We have added three more lines. These three additional lines are identified by "MyPageDataDef.maindatarequest". This data set is called "maindatarequest" because the values coming out of this select statement will populate the dynamic keys on the web page that are outside of any loop structures such as tables and list boxes. The keys inside loop structures are called loop data sets. Essentially every web page as a result has one main data set and 'n' number of loop data sets. I will cover the loop data sets in the next section
With this change if you now issue the URL http://localhost:8080/servlet/DisplayServlet?url=MyPageURL&arg1=satya, you will see the html page changed to the following:
Sample html page Demonstrates simple tags satya is my name Software is my occupation java is my preferred language c# is another language I work in
Where "java" and "c#" are retrieved as a single row from the database table "languages_table".
So far we have introduced two java classes as part of our page processing these are
com.ai.htmlgen.DBHashTableFormHandler1 com.ai.db.DBRequestExecutor2
These classes in Aspire are called "requestexecutors" or "parts". These are reusable assets that are pre-coded into aspire. The number at the end of the classname indicates the version number of the part. The previous releases of the parts are usually available in the same jar file to provide backward compatibility.
In the "maindatarequest" above "DBRequestExecutor2" will execute the select statement and return the row to Aspire as an object of type "IDataCollection. This means any java class that is capable of returning this interface can take the place of "DBRequestExecutor2" in this properties file. This is important because you can have parts that can read from flat files (com.ai.data.RowFileReader) and parts that can read using stored procedures (com.ai.db.StoredProcExecutor2), and so on and so forth. These are called compaitble part list. The compatible part list for DBRequestExecutor2 are
Depending on the part the part specification may change. To know the specification of a part you have to refer to the documentation for that part. As an example let us see how the above page definition changes when we use a file reader part as opposed to a database part
# Basic page definition MyPageURL=aspire:\\mydir\\dynamic-page1.html MyPageURL.formhandlerName=MyPageDataDef request.MyPageDataDef.form_handler.class_request.className=com.ai.htmlgen.DBHashTableFormHandler1 # Maindata request (Key/value pairs outside of loops) request.MyPageDataDef.maindatarequest.classname=com.ai.data.RowFileReader request.MyPageDataDef.maindatarequest.filename=aspire:\\mydir\\mydata.txt
See how the file reader takes ".filename" as part of its specification as opposed to a database alias name and a database statement.
On the web site of Aspire a good bit of sample properties files are included. If you look in these files for these part names you will see how to specify these parts.
Let us continue with our focus on painting the page. This time let us assume the "arg3" and "arg4" comes from two select statements. Let us also assume we need another argument called "arg5" (just for the demo). Let us now see how we can use three select statements to satisfy the "maindatarequest".
# Basic page definition MyPageURL=aspire:\\mydir\\dynamic-page1.html MyPageURL.formhandlerName=MyPageDataDef request.MyPageDataDef.form_handler.class_request.className=com.ai.htmlgen.DBHashTableFormHandler1 # Maindata request (Key/value pairs outside of loops) request.MyPageDataDef.maindatarequest.classname=com.ai.db.DBPreTranslateArgsMultiRequestExecutor request.MyPageDataDef.maindatarequest.db=request.MyPageDataDef.maindatarequest.request.1=Arg3SelectStmt request.MyPageDataDef.maindatarequest.request.2=Arg4SelectStmt request.MyPageDataDef.maindatarequest.request=Arg5SelectStmt # Individual request definitions request.Arg3SelectStmt.classname=com.ai.db.DBRequestExecutor2 request.Arg3SelectStmt.db= request.Arg3SelectStmt.stmt=\ select preferred_language as arg3 \ from languages_table \ where coder="satya" request.Arg4SelectStmt.classname=com.ai.db.DBRequestExecutor2 request.Arg4SelectStmt.db= request.Arg4SelectStmt.stmt=\ select alternate_language as arg4 \ from languages_table \ where coder="satya" request.Arg5SelectStmt.classname=com.ai.db.DBRequestExecutor2 request.Arg5SelectStmt.db= request.Arg5SelectStmt.stmt=\ select alternate_language as arg5 \ from languages_table \ where coder="satya"
Now the "maindatarequest" points to a "multirequestexecutor" part as opposed to a simple "dbrequestexecutor". The specification of "multirequestexecutor" allows to specify a series of individual requests that gets executed in sequence. The output from the select statements are combined to form a single row and delivered to the web page. Both "dbrequestexecutor" and "multirequestexecutors" are compatible and completely interchangeable. This is the flexibity of Asprie in general. And you will see this pattern reused lot of places.
For MicrosoftSQLserver issuing a stored proc is no different than issuing a SQL statement. But this is quite different for Oracle. For this reason a new part is created just for Oracle called "StoredProcExecutor2". This is how you invoke a stored proc instead of an sql statement on your page.
# Basic page definition MyPageURL=aspire:\\mydir\\dynamic-page1.html MyPageURL.formhandlerName=MyPageDataDef request.MyPageDataDef.form_handler.class_request.className=com.ai.htmlgen.DBHashTableFormHandler1 # Maindata request (Key/value pairs outside of loops) request.MyPageDataDef.maindatarequest.classname=com.ai.db.StoredProcExecutor2 request.MyPageDataDef.maindatarequest.db=request.MyPageDataDef.maindatarequest.stmt=\ {call oracle-pkg.storedproc1(?,'satya')}
The convention is that the first argument to the proc should be a question mark identifying an oracle result set. The subsequent arguments will be input arguments. The only output from the stored proc that is allowed is a single result set coming back as the first argument. If you need more sophistication you can write your own part. For SQL Server the above definition will look like
# Basic page definition MyPageURL=aspire:\\mydir\\dynamic-page1.html MyPageURL.formhandlerName=MyPageDataDef request.MyPageDataDef.form_handler.class_request.className=com.ai.htmlgen.DBHashTableFormHandler1 # Maindata request (Key/value pairs outside of loops) request.MyPageDataDef.maindatarequest.classname=com.ai.db.DBRequestExecutor2 request.MyPageDataDef.maindatarequest.db=request.MyPageDataDef.maindatarequest.stmt=\ {call oracle-pkg.storedproc1('satya')}
DBRequestExecutor2 is used for stored procedures for sql server, because calling a proc is same as calling SQL. The stored proc in this case should return a single result set as part of an internal select.
Let us continue the story by introducing two loops in to the page. One loop will tell us the benefits of java. This loop is called "java-benefits-loop". One loop will tell us the benefits of c#. This loop is called "c#-benefits-loop".
Sample html page Demonstrates simple tags {{arg1}} is my name {{arg2}} is my occupation {{arg3}} is my preferred language {{arg4}} is another language I work in Here are the benefits of java <!--RLF_TAG BGN_LOOP java-benefits-loop --> {{benefit}} <!--RLF_TAG END_LOOP java-benefits-loop --> Here are the benefits of c# <!--RLF_TAG BGN_LOOP c#-benefits-loop --> {{benefit}} <!--RLF_TAG END_LOOP c#-benefits-loop -->
Let us begin by going into the meaning of the "java-benefits-loop" tag. The tag is saying to look for a collection of rows (These can come from a select statement or a file etc.) and for each row repeat the html segment that is between the start and end tags. Let us now see how this loop name "java-benefits-loop" is tied to a select statement in the properties file.
# 1). Basic page definition MyPageURL=aspire:\\mydir\\dynamic-page1.html MyPageURL.formhandlerName=MyPageDataDef request.MyPageDataDef.form_handler.class_request.className=com.ai.htmlgen.DBHashTableFormHandler1 # 2). Maindata request (Key/value pairs outside of loops) request.MyPageDataDef.maindatarequest.classname=com.ai.db.DBRequestExecutor2 request.MyPageDataDef.maindatarequest.db=request.MyPageDataDef.maindatarequest.stmt=\ {call oracle-pkg.storedproc1('satya')} # 3). java-benefits-loop representing a select statement request.MyPageDataDef.java-benefits-loop.class_request.className=com.ai.htmlgen.GenericTableHandler6 request.MyPageDataDef.java-benefits-loop.query_request.className=com.ai.db.DBRequestExecutor2 request.MyPageDataDef.java-benefits-loop.query_request.db=as400dbAlias request.MyPageDataDef.java-benefits-loop.query_request.stmt=\ select benefit from language_benefits_table where language="java" # 4). java-benefits-loop representing a select statement request.MyPageDataDef.c#-benefits-loop.class_request.className=com.ai.htmlgen.GenericTableHandler6 request.MyPageDataDef.c#-benefits-loop.query_request.className=com.ai.db.DBRequestExecutor2 request.MyPageDataDef.c#-benefits-loop.query_request.db=as400dbAlias request.MyPageDataDef.c#-benefits-loop.query_request.stmt=\ select benefit from language_benefits_table where language="c#" # 5). The following line is useful if you directly retrieve the data MyPageDataDef.loopNames=c#-benefits-loop,java-benefits-loop
The loop name is tied to the data definition first. Together their combination points to a select statement through the database part called DBRequestExecutor2. There is an additional class that gets mentioned in case of a loop. Typically this additional class is called a table handler. The role of this class is a bit subtle. This class is responsible for adapting the output of a select statement to the needs of an html page. For example you want to do totals on some columns. Some times you want to remove columns and sometimes add columns. Sometimes you want to page through data 5 rows at a time. Sometimes you want to read through rows one by one. Some times you want to read them all into memory. These variations are provided by these adapter/strategy classes. The compatible table handlers at the time of build 18 are
Eitherway as you go designing page after page, it is a question of copying these definitions and changing the appropriate page related names. The java classes gets changed very infrequently. So the page defintion for each page looks very similar except for the select statements.
Using the loop structures above it is not difficult to imagine how one can create an html table quite easily. Here is an example.
(table) (tr) (th)col1(/th) (th)col2(/th) (th)col3(/th) (/tr) (!--RLF_TAG BGN_LOOP loopname --) (tr) (td){{column1}}(/td) (td){{column2}}(/td) (td){{column3}}(/td) (/tr) (!--RLF_TAG END_LOOP loopname --) (/table)
I have replaced typical html element tags with brackets for the sake of this writing. Please use the angular brackets when you use the code.
Same thing applies to list boxes. Just put one of the option tags in a loop element.
# Basic page definition MyPageURL=aspire:\\mydir\\dynamic-page1.html MyPageURL.formhandlerName=MyPageDataDef request.MyPageDataDef.form_handler.class_request.className=com.ai.htmlgen.DBHashTableFormHandler1 # Maindata request (Key/value pairs outside of loops) request.MyPageDataDef.maindatarequest.classname=com.ai.db.DBRequestExecutor2 request.MyPageDataDef.maindatarequest.db=request.MyPageDataDef.maindatarequest.stmt=\ select preferred_language as arg3 \ ,alternate_language as arg4 \ from languages_table \ where coder={string-arg1.quote} and anotherarg={int-arg2}
The argument names are kept inside of { and }. If the type of argument is string or date then append the arg name with ".quote" to indicate that the value needs to be placed inside of quotes. If the value is missing then this instruction will subtitute a null value without quotes. If the arg type is an integer or a non-string type then use it as it is. These arguments will be taken from the incoming url.
In addition the columns from the maindatarequest can be used in loop requests as argument names.
As you create page definitions you will be specifying these java classes to accomplish your work. How do you know which versions go together. Take a look at the release notes that comes with each build. The release notes have recommendations as to what is the compatible set of these java classes.
For instance all of the above classes are compatible as of build 18.