Creating Service Pages
Static pages are nice, but dynamic pages are nicer.
In this tutorial, dynamic page creation using services will be demonstrated.
Services provide an easy way to access processing and data. The BeNOW Web Framework
facilitates the requesting of data from services and the display of the resulting data.
After this tutorial, you should be able to create rich dynamic pages.
Contents
- Creating Services
- Calling a Service and Formatting Results
- Calling a Service if User Has Permission
- Posting Data to a Service
- Calling a Service from Javascript
- Developing Service Pages using the Web Development Page
Creating Services
To create services, please see the Creating Services tutorial.
We will use the TutorialService in the subsequent steps. Ensure that services
are locked down via the service security mechanisms. Once
the service is showing up on the service listing (localhost:8080)
it is ready to be used.
Calling a Service and Formatting Results
Calling a service from a page is a matter of using the svc tag. Open
up html/tutorial.page and find the template that matches service:
<xsl:template match="service">
<svc op="displayPeople" sig="test.org.benow.service.TutorialSecureService.getPeople()"/>
</xsl:template>
This will call getPeople() from test.org.benow.service.TutorialSecureService
and use the template displayPeople to display the results. The results are returned as
XML for input to the template that matches the value given for the op attribute:
<xsl:template match="displayPeople">
1) The full result from <code>
<a href="/svc/test.org.benow.service.TutorialSecureService">test.org.benow.service.TutorialSecureService.getPeople()</a></code>
is wrapped by <code>displayPeople</code> and is:<br/>
<textarea rows="20" cols="120"><xsl:copy-of select="."/></textarea>
<br/>
2) Without the page data, the <code>getPeople()</code> method <code>result</code> is<br/>
<textarea rows="20" cols="120"><xsl:copy-of select="result"/></textarea><p/>
3) The following people are known:
<div class="result">
<xsl:for-each select="result/item">
<xsl:sort select="last-name"/>
<xsl:sort select="first-name"/>
<xsl:value-of select="last-name"/>, <xsl:value-of select="first-name"/>
from <xsl:value-of select="address"/>, <xsl:value-of select="city"/>
<xsl:value-of select="postal"/><br/>
</xsl:for-each>
</div>
</xsl:template>
The page can be viewed at localhost:8080/tutorial.page. The
XML, returned from the call:
- has a root element of
displayPeople, which
causes the displayPeople template to be matched
- contains the same standard web state nodes as for page display, ie params, url, headers, session, etc. These
can be useful for state management and display.
- has a
result element which contains the XML representation of the
result of the method call.
In this case, getPeople() returns a list of Persons. The
XML representation of a list is <items><item>...</items>. The
XML representation of the returned objects is generated by the XML api, and
annotations can be used to tailor XML (see Using the XML API tutorial).
The displayPeople template displays the returned XML and formats and
displays the name and address of the known people.
So, the XML produced by the method is reformatted to the
desired output. This separation of processing and formatting
is one of the strenghts of XSL, as the coders can focus on processing well (good java code)
and presenting well (good html code)... the XSL merely reformats.
The disadvantage is that things can get a bit messy. There's often
quite a bit of XML produced by service methods and used as input to the
matching templates. The XML produced by services can be seen in the
XML browser by choosing the XML link in the top left of the page or hit the
Alt-X shortcut (hold down alt, shift and x).
Calling a Service if User Has Permission
Through the use of java functions within pages, it is possible to
selectively supress html production, which can be useful with service calls.
Each page has a <xsl:param name="user"/> which is a java
User object, representing the current user requesting the page (or the anonymous
user, if the visitor has not signed on). An example of this is the template
matching permissionedService in html/tutorial.page:
<xsl:template match="permissionedService">
Calling <code>getPeopleOfHeight(Integer)</code> if <code><xsl:value-of select="java:getName($user)"/></code>
has permission: <code>test.org.benow.service.TutorialService.invoke</code>
<xsl:choose>
<xsl:when test="java:hasPermission($user,'test.org.benow.service.TutorialService.invoke')">
<svc op="displayPermissionedPeople"
sig="test.org.benow.service.TutorialSecureService.getPeopleOfHeight(Integer)">
<heightInCM>180</heightInCM>
</svc>
</xsl:when>
<xsl:otherwise>
No <i>test.org.benow.service.TutorialService.invoke</i> permission
for <xsl:value-of select="java:getName($user)"/>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
This example can be run by hitting http://localhost:8080/tutorial.page?op=permissionedService.
If the user has the test.org.benow.service.TutorialService.invoke permission
then the services will be called to get known people over 180cm. The full signature of the
method is used test.org.benow.service.TutorialSecureService.getPeopleOfHeight(Integer)
with the parameter being passed in as named, heightInCM. The service invoker will
take care of converting the given parameter string ('180') into the
appropriate representation (an integer), and the service method will be invoked,
returning the results. If the user does not have sufficient permission, a message
will be displayed, instead of the service being invoked.
Using java:hasPermission($user,perm) can be useful to not display
service results or links to service templates if the user does not have permission.
In this way, the unpermissioned user is not even aware that extra functionality exists,
which lessens the ability to discover things no of concern, and keeps the returned html
cleaner. If a service is called to which the user has no permission, the service api
will assert the permission and an error will be returned, which is ugly outside of debugging.
Be reassured that if they do find out the existence of a service and method, they won't be able to
invoke it if they do not have permission.
Posting Data to a Service
Data may also be posted to services. This is useful when adding data
to data application, among other situations. An example of posting to a
service can be seen within the html/tutorial.page in the
template matching postingToServices and can be run by hitting
tutorial.page?op=postingToServices.
It contains:
<form action="/svc/test.org.benow.service.TutorialSecureService" method="post">
<input name="exec" type="hidden" value="addPerson(String,String,String,String,String,Integer)"/>
<input name="redirect" type="hidden" value="?op=service"/>
First Name: <input name="firstName"/><br/>
Last Name: <input name="lastName"/><br/>
Address: <input name="address"/><br/>
City: <input name="city"/><br/>
Postal: <input name="postal"/><br/>
Height in CM: <input name="heightInCM"/><br/>
<input class="buttonDefault" type="submit" value="Submit New Person"/>
</form>
The action of the form points to the service url, /svc/test.org.benow.service.TutorialSecureService
and has a method of post (get will also work, but is not great for
large posts). The hidden exec input specifies the method to invoke,
addPerson(String,String,String,String,String,Integer). The hidden
redirect parameter indicates a url to go to after the invocation. In this case
we want to show the results via the service operation of this page (it
parses the referrer url to know what the calling page was). The parameters
for the method are form inputs with names matching the parameters in the method:
firstName, lastName, address, city, postal and heightInCM. The submit button
simply submits the form.
When the service url is hit in this manner, the method is determined, the arguments are extracted
and converted from string (when appropriate) and the method is invoked. After the method
is invoked, the redirect url (if given) is redirected to. If no redirect parameter is
given, the referring page is displayed again.
After values are supplied and the form posted, a new Person is added and
the list of people is displayed via the template which matches service.
You should see your added person at the bottom of the page.
In more advanced use cases, the parameters for complex objects can be specified as XML.
The service handler will parse the XML into java objects before invoking the method. Files
may also be posted to services methods with java.lang.File parameters using
a multi-part form request. They will be received and stored temporarily and the method
invoked.
Form submission provides an easy way to get stuff into the web application.
Calling a Service from Javascript
Javascript calling of services is another feature of the Web API. It allows
for easy and natural javascript calls to services, without the heavy(ish) lifting
of AJAX. A demo of javacript calling can be found in the template matching
javascriptService in html/tutorial.page, and may be
run by hitting /tutorial.page?op=javascriptService (localhost:8080).
The page portion is:
<script src="/js/org.benow.util.Request.js">;</script>
<script src="/js/org.benow.util.DOM.js">;</script>
<script src="/svc/test.org.benow.service.TutorialSecureService.js">;</script>
Minimum height of people to fetch
<input id="height" size="4" type="text" value="180"/><br/>
<input type="button" value="Fetch People By Height"
onClick="var height=document.getElementById('height').value; getPeopleOfHeight(height)"/>
<input type="button" value="Fetch All People" onClick="getPeople()"/>
<div id="response"/>
This includes the /js/org.benow.util.Request.js,
which is required for service method invocation.
/js/org.benow.util.DOM.js is included for the getChildText(element,string)
method used in result display, and contains many helper methods for working with XML.
The service javascript /svc/test.org.benow.service.TutorialSecureService.js is also included.
By pointing a browser to /svc/test.org.benow.service.TutorialSecureService.js (localhost:8080)
you can see the javascript which has been created. This javascript creates the AJAX request and response handling, and
exposes the service methods as simple javascript functions, which can be seen
in the above example. When the user clicks on the 'Fetch People By Height' button
the height is fetched and getPeopleOfHeight(int) is called. The method
signature is exactly the same as in the java service definition. Calling it is
as simple as calling the method from java.
The javascript functions which receive and display the results are found
in html/tutorial.js, which is automatically included at page delivery time.
function onGetPeople(url,http_request) {
showPeople("getPeople()",http_request);
}
function onGetPeopleOfHeight(url,http_request) {
showPeople("getPeopleOfHeight(int)",http_request);
}
function showPeople(method,httpRequest) {
var result;
alert('got xml from: '+method+'\n'+httpRequest.responseText);
var items=responseXML.getElementsByTagName('person');
if (items.length==0)
result='No people found.';
else {
result=items.length+' '+(items.length>1?'people':'person')+' found.<br/>';
for (var i=0;i<items.length;i++) {
var currE=items[i];
var lastName=getChildText(currE,'last-name');
var firstName=getChildText(currE,'first-name');
var height=getChildText(currE,'height-inc-ms');
result+=lastName+', '+firstName+' '+height+'cm<br/>';
}
}
var respDiv=document.getElementById('response');
respDiv.innerHTML=result;
}
When a request is made to the method methodName in the service and
results are expected,
a function onMethodName(url,http_request) should be
defined (ie function onGetPeople(url,http_request) to handle
the getPeople() response). The url parameter is
the url of the service that was called, and the http_request is
the AJAX httpRequest object. The XML of the response is found as
http_request.responseXML and can be used for display or processing
of service method results. This can be seen in the above example as the for
loop, where all <person> elements are retrieved
and the first name, last name and height combined and displayed as the response.
The javascript interface to the services is quite refined. It is fast and
very easy to use. The lessened overhead of implementing javascript ajax calls means
that more time can be put into making things look and work well. It's less tedious,
and easier to maintain than standard AJAX.
Developing Service Pages using the Web Development Page
The task of creating a page which calls services is made easier by the
Web Development Page (localhost:8080).
When editing a page, there is a generate feature which can create the
template for calling services in various ways (ie via the <svc>
tag, by form submission and via javascript). It is still early days for this
page, but it might make your life a bit easier.
The BeNOW tutorials are a work in progress. If you have any comments or suggestions, please email <
andy@benow.ca>.