Calling services from a page
You now know how to create a page, but the pages so far have been simple, and
far from dynamic. The services for the music library have also been
created. We just now need to request
data from the services and display that data in the page. In such a way, the
page content becomes dynamic, and the application starts to emerge.
The <svc> Tag
As mentioned in the page creation step,
there is a post processing step in the assembly of the HTML (step 5). In this
step, the HTML assembled to this point is scanned for tags which mean something
to the web framework. One of those tags is the <svc> tag.
When an <svc> tag is encountered in the intermediary HTML, the tag is parsed,
and the specified service method is called, with the result returning to
a specified template.
Lets first create the walkthru/index.page, which will show an overview
of albums by artists, and within that page, make a call to AlbumService.getAlbums():
Take a look at the svc tag within the template matching index:
<svc sig="doc.walkthru.AlbumService.getAlbums()" op="dumpAlbums"/>
The sig attribute is the full signature of the method to call. It is prefixed by the full
class name of the service and the full name of the method (including parameters, if
any). In this case there are none, but if we were to call createAlbum, for example,
the signature would be doc.walkthru.AlbumService.createAlbum(String,Performer).
If the method requires parameters, the parameters can be specified as elements within
the svc tag. For example:
<svc op="dumpAlbum" sig="doc.walkthru.AlbumService.getAlbumByKey(Object)">
<key>1234</key>
</svc>
Calls a getAlbumByKey within the AlbumService with one parameter (named 'key' as
given in the @ParamName annotation of the service), with a value of 1234.
The op attribute of the svc tag specifies the template to be used to transform the results of the
method call. The result object of the method, if any, is marshalled to XML and
becomes the <result> element within the new input XML. The XML marshalling
is performed automatically by the XML API and requires no intervention, but the
objects may be tailored for XML display via annotations. See the
Using the XML API tutorial
for details.
Handling the Result
So, the call will be made to the service, and the service will return the result, which
is marshalled to XML which becomes the input to the specified template. In the
above page, I've specified that the result is to be handled by the dumpAlbums
template, which does nothing but display the input XML:
In this case the XML is dumped within a textarea by using the
<xsl:copy-of> function. The XML can also be viewed, as before,
by clicking on the XML link and choosing the appropriate service call from the dropdown:
The full input XML for the dumpAlbums template is:
<dumpAlbums uri="/walkthru/index.page">
<params>
<pagePath>walkthru/index.page</pagePath>
</params>
<url>http://localhost:8420/walkthru/index.page</url>
<headers>
<Host>localhost:8420</Host>
<User-Agent>Mozilla/5.0 (X11; U; Linux x86_64; en-US; rv:1.9.1.7pre) Gecko/20091210 Ubuntu/9.10 (karmic) Shiretoko/3.5.7pre</User-Agent>
<Accept>text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8</Accept>
<Accept-Language>en-us,en;q=0.5</Accept-Language>
<Accept-Encoding>gzip,deflate</Accept-Encoding>
<Accept-Charset>ISO-8859-1,utf-8;q=0.7,*;q=0.7</Accept-Charset>
<Keep-Alive>300</Keep-Alive>
<Connection>keep-alive</Connection>
<Referer>http://localhost:8420/admin/dev/index.page?op=view&type=page&item=walkthru/index.page</Referer>
<Cookie>userInfo=admin 92668751; userInfo=admin 92668751; wdk_pref_cookie_0=eJyNkctKQzEQhl/F7uXQikIrzbLo5mjBLhSRME3GdmiSCZlp63l7o1WoR7wsB/7L9zOuoMekBEFsBpE9F2/w+ua+PZOL29l4f3clk+nAccycqvCRvAm8ovTUbAVLgojGO43TwY6ElgELZhZSLoRiLkfn48mpj9Zj5JpyVNZzQ86BHChxasA5lBpGgbRrZM1F3VYT7Gj1LsAEtcib55qDv1khqOKL9vQuUIVIqHXrJvDBaaydtfPFw2Lezqw9+crq2S1BKurnkJ8rN9gtGYr/H+1W128dH6fnCPSd5NjhSXKArnGh/oqcGQ2HfdRDBsasnf2jrrfqFRPUx5M.; JSESSIONID=1rqtxca19c9ez</Cookie>
<Cache-Control>max-age=0</Cache-Control>
</headers>
<session>
<headers>{}</headers>
<addCookies>[javax.servlet.http.Cookie@13d3f62]</addCookies>
<pagexml>[org.benow.web.path.page.XMLChunk@9f9761]</pagexml>
<xslParams>{}</xslParams>
<params>pagePath{walkthru/index.page} </params>
<user key="1" type="org.benow.repository.security.UserImpl">
<name>admin</name>
<description><![CDATA[Administrative user.]]></description>
<validated>true</validated>
<validation-code>1260832613816</validation-code>
<needs-password>false</needs-password>
<confirmed>true</confirmed>
</user>
<url>http://localhost:8420/walkthru/index.page</url>
<userStamp>Thu Dec 17 18:55:04 MST 2009</userStamp>
</session>
<method signature="getAlbums()">
<name><![CDATA[getAlbums]]></name>
<in-class>doc.walkthru.AlbumService</in-class>
<return-argument is-simple="false" required="true" type="org.benow.java.spec.argument.Argument" unary="false">
<name type="java.util.ArrayList">
<item>return</item>
</name>
<type>java.util.List</type>
<is-collection>true</is-collection>
<contained-class>doc.walkthru.Album</contained-class>
</return-argument>
</method>
<result type="org.benow.repository.util.PersistentArrayList">
<items end-index="1" from="0" super-count="2" type="org.benow.repository.util.PersistentArrayList">
<size>2</size>
<item key="1" type="doc.walkthru.AlbumImpl">
<performer key="1" type="doc.walkthru.ArtistImpl">
<name>Aphex Twin</name>
</performer>
<title>Selected Ambient Works 85-92</title>
</item>
<item key="2" type="doc.walkthru.AlbumImpl">
<performer key="1" type="doc.walkthru.GroupImpl">
<name>DATacide</name>
</performer>
<title>Flowerhead</title>
</item>
</items>
</result>
</dumpAlbums>
The <dumpAlbums> element causes the template matching dumpElement
to be called. Within the the input XML is dumpAlbums/result which is
the XML representation of the object returned the service method, in this case, a list
of albums (as previously populated in the Calling services from Java section):
<result type="org.benow.repository.util.PersistentArrayList">
<items end-index="1" from="0" super-count="2" type="org.benow.repository.util.PersistentArrayList">
<size>2</size>
<item key="1" type="doc.walkthru.AlbumImpl">
<performer key="1" type="doc.walkthru.ArtistImpl">
<name>Aphex Twin</name>
</performer>
<title>Selected Ambient Works 85-92</title>
</item>
<item key="2" type="doc.walkthru.AlbumImpl">
<performer key="1" type="doc.walkthru.GroupImpl">
<name>DATacide</name>
</performer>
<title>Flowerhead</title>
</item>
</items>
</result>
Each result/item is an album, with a performer and a title. So, now we have
a result from the service, and realize how it is represented in the input, we
can use XSL to format it:
For each album (result/item), ordered by performer name, the
performer name is output in bold and then the title:
That's it. The XML representation of the object returned by the service method is
transformed into nice HTML.
Linking the Pages
We can now make this into a real web app by linking things
together:
So, the performer name links to the performer page using the performer key and type and
the album links to the album page using the album key, but, there are no performer and
album pages. Clicking on the links would typically cause a 404, document not found, but
we now run into another feature of the web framework. If the current user is a web developer
(ie has the WebDevelopmentService.invoke permission), or is the admin (who automatically
has all permissions), then the following helper page is shown:
Hitting 'Yes' causes the referenced page to be automatically created (as it would have
been if the development page were used directly). If the user were not a web developer,
then they would have received a 404. This auto-create facility is another tool to
speed development.
Similarly, if you were to define a call to a service method which did not exist,
it would create it. I called this from album.page
<svc op="dumpAlbum" sig="doc.walkthru.AlbumService.getAlbumByKey(Object)">
<key><xsl:value-of select="@key"/></key>
</svc>
without AlbumService.getAlbumByKey(Object) having been declared, and
the following page is presented:
When 'Yes' is clicked, it will create the method. If it determines that the service
(AlbumService in this case) is an interface, it will create the method
in the interface and the implementation (AlbumServiceImpl), if it is
found. After create, the following now exists and can be implemented. In AlbumService:
public void getAlbumByKey(
@ParamName("pagePath")
Object pagePath);
and in AlbumServiceImpl:
public void getAlbumByKey(
Object pagePath) {
throw new org.benow.util.NotImplementedException("getAlbumByKey(Object)");
}
The code needs tuning and implementation, but it speeds development.
So, with the pages linked up, it's now more of a web application. Albums and Performers
are presented, using calls to the services, which make requests of the repository. It's
pretty, but not very useful without the ability to add information. I'll now add
information via posting to a service.