Happy New Year 2010
After a busy December month, the Christmas vacation finally gave me some room to concentrate on what really matters.
Here’s my third blog post on building web apps with Ontopia. A bit late – I had hoped to have it out before Christmas – but at least I made it within January 01 2010 … have a good one!
Getting to the Point
This blog post is part of a series on Web Application Development with Ontopia.
- Part 1: Installation & Requirements
- Part 2: Creating the Database
- Part 3: Creating the JSPs
In Part 2 of this series on building web applications with Ontopia we had a look at how to set up an application’s domain model using Ontopoly.
This post discusses how to go about building a custom web interface to present the data.
We’ll start by looking at what views we need to build. Then, we’ll have a look at how to build the blog post index (front page) and a page capable of displaying single posts.
The category and tag pages are very similar to these two, so you should be able to either do this on your own, or by looking at the provided source code. There’s also a live demo.
In order to limit the length of this blog post, I’ll discuss the search in a later entry.
Bear in mind that this is just an example; it may be modified in any way in order to build any kind of Ontopia driven web application — such as e.g. NRK/Skole.
Custom Code
Having looked at our blog model through the last couple of blog posts, what we need to build is hopefully pretty clear: we need interfaces presenting information about our topics.
In effect, this means creating a couple of JSPs to get a fully functional web application up and running. As mentioned in Part 1, we’ll place our code in the webapps folder of the Apache Tomcat distribution that comes bundled with Ontopia (inside a new folder called onto-web-app).
The files we need to create are:
- index.jsp (front page / blog)
- post.jsp
- category.jsp
- tag.jsp
- type (for generic topic type view, e.g. categories and tags)
- forwarder.jsp (for forwarding requests to proper JSPs)
- includes\
- global.jsp
- template.jsp
- tolog.jsp
- related.jsp
The only thing to note here is the search page – search.jsp as well as the includes folder and its contents: global.jsp will contain common JSP directives such as e.g. taglib declarations and global imports while template.jsp is the site template. We put common tolog code in tolog.jsp.
tolog
Before we get started, let’s have a (very) quick look at the query language that we will use to query our topic map for information; tolog.
tolog is a topic map query language developed by Ontopia. It’s kinda like SQL for topic maps.
With tolog you may query for topics of given types, topic or occurrences with given names or values, topics that play specific roles in associations, find topics associated with another topic, search for topics whose name match a given search string, etc. In short anything that is stored in your topic map.
Basic Example
Given a topic map with a Person topic type identified by the ID person, you may fetch a list of all persons by issuing the following query:
instance-of( $person, person )?
Executing the above tolog query would give us all persons in the topic map (bound to $person).
We may also fetch the persons and their birth date by executing the following query:
instance-of( $person, person ), birth-date( $person, $date) ?
(given that birth-date identifies an occurrence type `birth-date’).
Or the persons born on a given date:
instance-of( $person, person ), birth-date( $person, "1979-12-08") ?
Learn more about tolog by checking out the tolog tutorial and the tolog predicate reference.
Blog Frontpage: index.jsp
For our blog front page, we want to present a list of the most recent 10 or so blog posts along with their title, publish date and excerpt.
We get this information by using a simple tolog query.
Fetching Blog Posts
Remember that in Part 2, we added Subject Identifers (SIs) to our topic types? We can now use the SIs to refer to the topic types from tolog queries.
In our model, the Blog Post concept is identified by the URI http://psi.ontopedia.net/Blogging/Post. Given that all blog posts we write are instances of this concept — or objects of this class, if you wish — we can fetch a list of blog posts using the following tolog query:
instance-of( $post, i"http://psi.ontopedia.net/Blogging/Post") ?
As you can see from trying out the above query live, it will return a set of all blog posts stored in our topic map.
We’re looking for the most recent ones, sorted by date, though.
In order to accomplish this, we alter the query – introducing the publish-date – and order the result set according to the value of the publish-date occurrence (note also the use of namespaces):
using blog for i"http://psi.ontopedia.net/Blogging/" using tobs for i"http://example.topicobserver.com/psi/" instance-of( $post, blog:Post ), tobs:publish-date( $post, $date), order by $date desc limit 10?
Nice? Now all we have to do, is to incorporate this into a JSP and format it according to our needs.
Luckily, Ontopia provides a tag library for working with tolog queries. The tolog tag lib defines tags such as e.g.
<tolog:foreach /> <tolog:if /> <tolog:out /> <tolog:set />
By using the <tolog:foreach /> tag with our query, we can easily iterate over and output information from the query result set.
Our first draft of index.jsp looks like this:
<%@ page language="java" contentType="text/html; charset=UTF-8" %>
<%@ taglib uri='http://psi.ontopia.net/jsp/taglib/tolog' prefix='tolog'%>
<html>
<head>
<title>My Blog</title>
<!-- some stylesheets and stuff here -->
</head>
<body>
<!-- site navigation etc here -->
<div id="content">
<h1>My Blog</h1>
<!-- sets the context of our tolog queries to the correct topic map -->
<tolog:context topicmap="blog_topic_map.xtm">
<!-- iterate through the 10 latest blog posts, sorted by date -->
<tolog:foreach query='
using blog for i"http://psi.ontopedia.net/Blogging/"
using tobs for i"http://example.topicobserver.com/psi/"
instance-of( $post, blog:Post ),
tobs:publish-date( $post, $date)
order by $date desc
limit 10?'
>
<!-- output the name of the post -->
<tolog:out var="post" /><br />
</tolog:foreach>
</tolog:context>
</div>
</body>
</html>
And voila, we have a simple index of our blog (live demo).
Well, a bit too simple, so lets add some data to it and make clickable headings out of the post titles.
We use <tolog:id /> to output the ID of a given object and pass this on to our post.jsp page as a URL query parameter.
<%@ page language="java" contentType="text/html; charset=UTF-8" %>
<%@ taglib uri='http://psi.ontopia.net/jsp/taglib/tolog' prefix='tolog'%>
<html>
<head>
<title>My Blog</title>
<!-- some stylesheets and stuff here -->
</head>
<body>
<!-- site navigation etc here -->
<div id="content">
<h1>My Blog</h1>
<tolog:context topicmap="blog_topic_map.xtm">
<tolog:foreach query='
using blog for i"http://psi.ontopedia.net/Blogging/"
using tobs for i"http://example.topicobserver.com/psi/"
instance-of( $post, blog:Post ),
tobs:publish-date( $post, $date)
order by $date desc
limit 10?'
>
<h2>
<a href="post.jsp?id=<tolog:oid var='post' />">
<tolog:out var="post" />
</a>
</h2>
<%-- may contain html, so we wrap it in a div instead of a p --%>
<div class="excerpt">
<a href="post.jsp?id=<tolog:id var='post' />">
<tolog:out query='
using tobs for i"http://example.topicobserver.com/psi/"
tobs:excerpt( %post%, $excerpt )? '
/>
</a>
</div>
</tolog:foreach>
</tolog:context>
</div>
</body>
</html>
Note the use of the %post% query parameter referring to the current topic bound to the $post variable. View live demo.
We’re now ready to construct the post.jsp page so that there will actually be something at the end of the links produced on the blog index.
Before we do so, let’s extract code that will be common to all of our JSPs in order to avoid code duplication. We place the common code in a template.
Using Templates
Typically, a site will contain markup that is common across different sections; for example the site’s header (e.g. HTML head and global navigation) and footer (e.g. copyright notice, quick links, etc.).
In “plain” Ontopia web apps — like the one we’re building — this markup can be added to a template file consisting of static markup as well as “placeholders” for dynamically created content.
We’re going to use a single template for all the views in our blog application.
Blog Template
The template for our site is pretty simple as it contains a limited amount of HTML and JSP tags. Below is a draft of a single template for our application.
<@ include file="/includes/global.jsp" @>
<html>
<head>
<title>My Blog</title>
<link rel="stylesheet" type="text/css" href="style/site.css" media="screen" />
</head>
<body>
<p class="skip2content"><a href="#content">Skip to content</a>.</p>
<nav>
<a href="/blog/">Home</a>
<a href="/blog/categories.jsp">Archive</a>
<a href="/blog/search.jsp">Search</a>
</nav>
<div id="content">
<%-- placeholder for main content area --%>
<template:get name="content" />
</div>
</body>
</html>
As you can see it’s all standard HTML except from a template tag that is part of Ontopia’s template tag lib.
We use the <template:get /> tag in order to get and insert markup that is put into the template from JSPs using the template. JSP directives are placed in global.jsp for re-use across JSPs.
We then modify index.jsp using our new template:
<%@ include file="/includes/global.jsp" %>
<template:insert template="/includes/template.jsp">
<tolog:context topicmap="blog_topic_map.xtm">
<%@ include file="/includes/tolog.jsp"%>
<template:put name="content">
<tolog:foreach query="
instance-of( $post, blog:Post),
tobs:publish-date( $post, $date)
order by $date desc
limit 10 ?">
<h2>
<a href="post.jsp?id=<tolog:id var='post' />">
<tolog:out var="post" />
</a>
</h2>
<div class="excerpt">
<a href="post.jsp?id=<tolog:id var='post' />">
<tolog:out query=" tobs:excerpt( %post%, $excerpt )? " escape="false" />
</a>
</div>
</tolog:foreach>
</template:put>
</tolog:context>
</template:insert>
We use the template by enclosing our code within a <template:insert /> element, feeding it the name of our template file. We then “place” the JSP generating output inside a <template:put /> element referring to the content placeholder that we added to the template. If we had multiple placeholders, we’d use multiple <template:put /> elements.
global.jsp contains the following:
<%@ taglib uri='http://psi.ontopia.net/jsp/taglib/template' prefix='template'%> <%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c"%> <%@ taglib uri='http://psi.ontopia.net/jsp/taglib/tolog' prefix='tolog'%> <%@ taglib uri='http://psi.ontopia.net/jsp/taglib/portlets' prefix='portlets'%> <%@page language="java" contentType="text/html; charset=UTF-8" %>
…and tolog.jsp contains:
<tolog:declare> using onto for i"http://psi.ontopedia.net/Blogging/" using tm for i"http://psi.ontopedia.net/Topic_Maps/" using tobs for i"http://example.topicobserver.com/psi/" using sioc for i"http://rdfs.org/sioc/ns#" using dc for i"http://purl.org/dc/elements/1.1/" using dctm for i"http://psi.topicmaps.org/iso29111/" </tolog:declare>
(More on additional tag libs below).
Blog Post
Now that we have the template in place, we’re ready to create the JSP for displaying blog posts.
By using the <tolog:set /> tag, we instantiate an object from the ID parameter passed from the front page. This object, which represents the current blog post, is bound to a named variable which we can refer to later on. We use it with <tolog:out /> to output information such e.g. occurence values (post excerpt, publish date, etc.).
<%@ include file="/includes/global.jsp"%>
<template:insert template="/includes/template.jsp">
<tolog:context topicmap="blog_topic_map.xtm">
<%@ include file="/includes/tolog.jsp"%>
<tolog:set var="post" reqparam="id" />
<template:put name="content">
<div class="post">
<h2><tolog:out var="post" /></h2>
<p class="pub-date">
Published on <tolog:out query='{ tobs:publish-date( %post%, $date ) | $date = "unknown" }?' />.
</p>
<div class="excerpt">
<tolog:out query="tobs:excerpt( %post%, $descr )? " escape="false" />
</div>
<div class="body">
<tolog:out query="tobs:body( %post%, $body )?" escape="false" />
</div>
</div>
</template:put>
</tolog:context>
</template:insert>
Simple as that.
Linking to Related Topics
We have now created the blog’s index and a view for single posts. Our single post view lack some vital information though; there are no links to categories or tags.
We could of course output these by using complex queries for fetching lists of categories or tags that are related to the current post. This would work fine, but would be somewhat restrictive.
Instead, we want to link to any associated topic (in a generic way) so that in the case where we extend our model — for instance by adding a “technology” topic and a post <--discusses--> technology association, we wouldn’t have to modify our JSPs in order to make the new associations appear on our pages.
We accomplish this by using a new tag from Ontopia tag lib; <portlets:related />.
The <portlets:related /> tag lets us easily link to a given topic’s associated topics. We can also exclude associations that we do not want to display on our pages.
As this is code that is likely to be re-used, we place it in a separate file; related.jsp under includes:
<%@ include file="/includes/global.jsp"%>
<tolog:context topicmap="blog_topic_map.xtm">
<tolog:set var="currTopic" reqparam="id" />
<portlets:related topic="currTopic" var="headings">
<%-- don't want to output surrounding div unless there are related topics --%>
<c:if test="${ fn:length(headings) gt 0 }">
<div class="related">
</c:if>
<c:forEach items="${headings}" var="heading">
<c:if test="${fn:length(heading.children) gt 0}">
<h3><c:out value="${heading.title}" escapeXml="false" /></h3>
<ul>
</c:if>
<c:forEach items="${heading.children}" var="assoc">
<li>
<a href="forwarder.jsp?id=${assoc.player.objectId}">
<tolog:out query=" $x = @${assoc.player.objectId} ?" />
</a>
</li>
</c:forEach>
<c:if test="${ fn:length(heading.children) gt 0 }">
</ul>
</c:if>
</c:forEach>
<c:if test="${ fn:length(headings) gt 0 }">
</div>
</c:if>
</portlets:related>
</tolog:context>
Again: simple as that. A single element which takes care of outputting all kinds of relations.
If we for instance wanted to exclude the written-by association, we would simply have to add excludeAssociations="tobs:written-by" to the portlets:related tag.
Blog post view with blog entry in the main content area and links to associated topics on the right.
Further Work
Revised Model
You may have noticed that while we store the title and description on the blog topic itself (see Part 2), the same information is hardcoded in our template.
Wouldn’t it be better if the title and description were fetched from the topic map?
Also, the blog topic type and the blog <-> post association indicates that there can be more than one blog in our application. The example code does not account for it, though, as any post with a publish date would be listed on the index page.
What this means, is that our model may contain too much information.
You may choose to simply delete the blog <-> post association, assuming that there is only one blog in your universe, for instance. Should you happen to need more than one blog, then the blog id (or perhaps a SID) would have to be included in a modified query on the blog front page, assuring that the selected blog posts were from the given blog (hence you may need an index of blogs even).
Publish Status
Our queries does not check the status of blog posts, and you may also navigate to the Publish Status topic via the related topics links. This page will lead you to the Draft topic’s page which in turn will list all drafts. Clearly not too secure.
This can be fixed by adding a new condition to our queries, ensuring that we only select from the post’s whose publish status is `Published’. An example of such a query would be:
using blog for i"http://psi.ontopedia.net/Blogging/" using onto for i"http://psi.ontopedia.net/" using tobs for i"http://example.topicobserver.com/psi/" instance-of( $post, blog:Post ), tobs:publish-date( $post, $date), tobs:has-status( $post : blog:Post, tobs:published : onto:status ) order by $date desc limit 10?
Note that this particular query would require a change to the model from Part 2: adding a new Subject Identifier field to the topic type “Publish Status” and setting the value for at least one of the Publish Status instances. The reason being that we’re querying for the blog posts that are related to the topic whose subject identifier equals http://example.topicobserver.com/psi/published (included with example code).
Hard Coded References
You may have noticed that the example code contains duplicated references to our topic map (`blog_topic_map.xtm’). A better way to set the context could be to pass the topic map ID on as a query parameter between JSPs.
<tolog:context topicmap='<%= request.getParameter("tmid") %>'>
Or, even better, we may set it as a context parameter in our web config (web-inf/web.xml). It would then be available through the pageContext:
<tolog:context topicmap='<%= pageContext.getServletContext().getInitParameter("tmid") %>'>
Better Use of Templates
In my opinion, it would be advisable to make better use of our template, adding more placeholders to it and dynamically insert data depending on which JSP we’re in. As the code stands now, there is some duplication in for instance tag.jspand category.jsp (these two are nearly identical), all pages contain a <h2>, etc.
Too Much Code in JSPs
If you prefer to minimize the amount of tolog in the JSPs, you may want to look into Ontopia’s Java API. You may then create your own POJO model with domain objects, factories, etc. limiting the code within your JSPs to accessing the model’s getters.
To me, this is like fishing for your aquarium’s gold fish with dynamites, though. An application like this will only ever contain a limited amount of JSPs and creating theoretically perfect JSPs is likely to cost you more work than maintaining the original ones would. But by all means, if your application calls for it, then use the API.
Moving On
The next blog post will explain how to add a search page to our application.
This will include a standard search form, simple search with tolog, as well as a search result view including filtering functionality like seen in many topic maps driven applications (see e.g. Government.no where you can filter search results by clicking the links under “Limit Search”).
Future code will also include some of the enhancements discussed above, and will be posted within this month!
Chris Conrey
/ 04/01/2010Nice tutorial, I like the way you really break this down. It’s a shame though that everyone writes a blog engine for a tutorial.
Trond
/ 04/01/2010@Chris Conrey
Thanks, glad you like it.
I understand your sentiments towards blog tutorials.
The problem is that the example has to be from a domain that most readers can relate to. When I started writing the series, a CMS-ish approach seemed like a good candidate, but in hinsight I’m a bit sorry I didn’t choose a pure CMS — with sites instead of blogs and articles instead of blog posts. The difference is only minimal, though.
In a future blog post, I hope to be able to show how the approach can be applied to any kind of domain, be it a CMS, an application for managing Customer data, Oil Rigs, or whatever.
Robert Barta
/ 29/01/2010Extremely insightful, I might say. But it also put my cat into overdrive:
http://kill.devc.at/node/309
Sigh. \rho
Paul Harris
/ 20/12/2010Nicely defined and well written, i must say!
Petr
/ 23/01/2011Hey Trond, can U contact me through email? I would like to discuss something about this manual….is it possible?
Thank U,
Petr
Dave Thompson
/ 26/02/2011Hi,
I’ve been really struggling with Ontopia and your tutorials help immensely. Did you ever write the Search tutorial…or do you plan to do so?
Thanks,
Dave Thompson
Trond
/ 08/03/2011@Dave,
Thank you — glad to be of help
I never did write that tutorial, but just posted a (short) post on simple topic maps search: http://www.topicobserver.com/blog/semantic-web/tm/2011/searching-a-topic-map-with-ontopia/
-Trond