This is a good place to start if you are not sure what Mojasef is, or what it has to offer. Installation, basic configuration, and some of the major features are introduced in a comfortable step-by-step approach.
ContentsIn the time-honoured tradition, let's start with "hello, world". In keeping with the principles described above, we want to be able to reuse as much as possible that we have learned before. So let's start with an old "Hello.java" that we might have lying around after learning Java in the first place:
public class Hello
{
public void sayhello()
{
System.out.println("Hello, World!");
}
public static void main(String[] args)
{
Hello hello = new Hello();
hello.sayhello();
}
}
To put this program on the web, we need to make the following changes to it: none at all :)
To use this program with Mojasef:
java -Dhttp.application$=Hello -jar moja-http.jar
If all that went according to plan, you should see "Hello World!". That was pretty straightforward, wasn't it.
As an aside, note the "$", that little character is actually part of some key technology that gives mojasef its simple and flexible configuration power. See the manual for more details.
Counting HitsNow, this is pretty simplistic, so far. So let's tighten it up a bit. First, Our "Hello" application may only be a dozen lines, but it's still too big. So let's remove some redundant code. We don't need any of that "main" nonsense, becuse mojasef already knows how to create an object and call a method.
public class Hello
{
public void sayhello()
{
System.out.println("Hello, World!");
}
}
That all looks neat enough, but it doesn't actually do much so far. How about remembering things between page requests? To keep it simple for this example, we'll just make it count accesses.
public class Hello
{
int n = 1;
public void sayhello()
{
System.out.println("Hello, World!");
System.out.println("visit number " + n++);
}
}
Try it. Stop the server (using Control-C, task manager or whatever), recompile Hello.java, and run Mojasef again. Refresh your browser a few times to see that it really is counting. Cool. And way simpler than messing about with the Servlet API.
OK, those of you with servlet experience might be beginning to worry. Relying on member variables in a multi-threaded situation is extremely risky and a classic servlet problem. So this is a little simplistic as it stands. If you are getting a lot of hits and you really care about your hit count being "absolutely correct", you should probably wrap the increment in a synchronized block. On the other hand, you could sidestep the problem by reading the next section.
User SessionsCounting is good, but sometimes we don't want to share a single value. How about if we want to keep a separate count for each visitor? This might seem tricky, but not in mojasef. Just run the server using:
To test this yourself you will either need to access it from different machines, or using different browsers. Firefox and Internet Explorer, for example. Point both your browsers at the same URL and try different combinations of refreshing the page to prove to yourself that the counts really are independent.
Permanent ConfigurationsAt this point I'd be getting fed up with typing such a long command line. Luckily Mojasef allows you to store stuff like this in a file ready to be picked up when the server starts. Here's a simple way to do that:
http.application$=http.SessionWrapper Hello
java -jar moja-http.jar
While playing with this, you may have noticed that it's got a pretty narrow view of what URLs to respond to. Anything other than /sayhello gets a 404 "not found" error. There are several ways to improve this, but one of the simplest is to rename the method from sayhello() to request(). request() is one of the method names that Mojasef looks for if it can't find a match on the URL.
public class Hello
{
int n = 1;
public void request()
{
System.out.println("Hello, World!");
System.out.println("visit number " + n++);
}
}
Now our server is much more polite. It will say "hello" to all visitors, whatever URL they ask for.
As an aside, Mojasef will always look for specific names before trying more general options like request, so we can add another method:
public class Hello
{
int n = 1;
public void request()
{
System.out.println("Hello, World!");
System.out.println("visit number " + n++);
}
public void secret()
{
System.out.println("you found the hidden page :)");
}
}
Now pretty much every URL we can think of (such as http://localhost/wibble, or whatever) will give us the "Hello, World" message , but if we happen on http://localhost/secret we get the other message. Anyone for web services without all that fuss?
Finding things outPlacing our raw objects on the web is a cool thing to do, but so far, not really as useful as the ugly Servlet API. If nothing else we need to be able to look at the parameters passd to GET and POST requests, and maybe other things such as request headers, cookies, and system configurations. Luckily, Mojasef comes to our aid with this too.
There are two major ways to get access to this kind of context information. I'll break with tradition in this simple tutorial, and introduce both of them, so you can get the idea of the possibilities. For the first approach, let's go back to our basic "hello" program and add a parameter to our method:
import org.stringtree.StringFetcher;
public class Hello
{
int n = 1;
public void sayhello(StringFetcher context)
{
System.out.println("Hello, World!");
System.out.println("visit number " + n++);
}
}
As you can see, we have eventually needed to import an extra class. Don't worry, though. This class doesn't carry a lot of baggage. StringFetcher is a very simple interface consisting of two methods:
If you want to fetch something in the form of a String from the surrounding context, use get(String key). This method returns the string value of the named context object, or the empty string ("") if not present.
If you want to fetch any other type of object, use getObject(String key) and cast the result if necessary. Note that if there is nothing stored with the named key, this methjod will return null, so it can also be used to check for the presence or absence of a value.
To compile this class you will need to make sure that StringFetcher is in your compilation classpath. The simplest thing to do at the moment is to just put the whole moja-http.jar in the classpath. Later, you may prefer to use the much "lighter" stringtree-if.jar from the Stringtree project, which contains all the interfaces needed to write applications that use Mojasef, but contains no implementation code to slow down and clutter up compilation.
To compile the class either place stringtree-if.jar or moja-http.jar in your classpath or use:
javac -classpath moja-http.jar Hello.java
As an aside, when Mojasef starts up, it will look for class directories and jar files in a lib directory, so when you find that you want to use external APIs or libraries in your applications or components, just pop them in there.
Now we have a context to look in, we really ought to use this new parameter to fetch some information. let's print the originating IP address of the request and the browser identification header, and (to make our greeting more personal), the value of a supplied "name" parameter.
import org.stringtree.StringFetcher;
public class Hello
{
int n = 1;
public void sayhello(StringFetcher context)
{
System.out.println("Hello, " + context.get("name");
System.out.println("visit number " + n++);
System.out.println("from IP: " + context.get("remote.address"));
System.out.println("browser: " + context.get("http.request.header.User-Agent"));
}
}
If you pop to the browser and request your mojasef URL (probably still http://localhost/sayhello), you should see something starting with
If you want something other than an empty string to appear when no parameter is supplied, you can easily set a default value in the same place you sett http.application. Add the following line to http.spec and restart the server:
name=Mystery Guest
Now, viewing http://localhost/sayhello gives:
This is all simple enough, but the output is beginning to look a bit messy. Despite using "println" to put our text on separate lines, the web browser is treating it as HTML, and displaying it all on one line. A simple way to fix this is to set the output "content type" to text/plain to override the default. You may notice that the StringFetcher we are passing in to this method only has "get" methods, If we want to be able to "put" output values as well, we need to pass in a StringRepository instead. Let's modify our class by changing the import statement and the parameter declaration:
import org.stringtree.StringRepository;
public class Hello
{
int n = 1;
public void sayhello(StringRepository context)
{
System.out.println("Hello, " + context.get("name");
System.out.println("visit number " + n++);
System.out.println("from IP: " + context.get("remote.address"));
System.out.println("browser: " + context.get("http.request.header.User-Agent"));
}
}
If you compile and run this, you'll notice that it behaves exactly as it did before. A StringRepository is also a StringFetcher, and provides all the same methods and results. StringRepository also provides two extra methods, with pretty obvious uses:
Actually, StringRepository also provides a clear() method, but using it in a Mojasef application can be a very quick way to screw up your program unless you know exactly what you are doing.
To set the page content-type, let's add a line to put "text/plain" back into the context:
import org.stringtree.StringRepository;
public class Hello
{
int n = 1;
public void sayhello(StringRepository context)
{
System.out.println("Hello, " + context.get("name");
System.out.println("visit number " + n++);
System.out.println("from IP: " + context.get("remote.address"));
System.out.println("browser: " + context.get("http.request.header.User-Agent"));
context.put("http.response.header.Content-Type", "text/plain");
}
}
Compile the class and restart the server. You should now see your messages in glorious plain-text-o-vision.
There are, of course, pleanty of other things you can put back into the context. Anything with a name starting with http.response.header. will be sent as an outgoing header. You can set the HTTP response code (e.g. 404 for missing page) using http.response.code, and set outgoing cookies by starting names with http.response.cookie.. You can also set any other value you like, to be used in a page template, but that will be introduced in a little while, below. For more details of any of this, see the manual.
Sharing context between several methodsRemember I mentioned another way of getting information from the server? If we want to keep our methods with no parameters (this is more usual when there are several other pre-existing methods in the class which we also want to make available on the web), we can make use of another mojasef feature. Before calling any method on our object, mojasef will first try the special method warmup. Just as with request, mojasef will look for warmup(StringRepository context), warmup(StringFetcher context), or warmup(). If you provide any of those public methods, mojasef will call it, and you can keep a copy of the supplied context if you want:
import org.stringtree.StringRepository;
public class Hello
{
int n = 1;
StringRepository context;
public void warmup(StringRepository context)
{
this.context = context;
}
public void sayhello()
{
System.out.println("Hello, " + context.get("name");
context.put("http.response.header.Content-Type", "text/plain");
}
public void secret()
{
System.out.println(context,get(name) + ", you found the hidden page :)");
}
}
Making nice-looking pages
Interesting as that side-trip into sending our page as plain text was, it's not much use for the bulk of web pages. Most web pages use HTML, and use markup such as <a href> for links and <p> for paragraphs. If we want to put this sort of stuff in our pages, we've got quite a few choices. Popular approaches used with the Servlet API include:
There are probably more. Realistically, you can use any of these with Mojasef, too. Mojasef also provides a few alternative options that (I feel) integrate better with the core application. To the above choices, Mojasef adds:
To show how this works, let's go back a bit, to our application without the text/plain:
import org.stringtree.StringRepository;
public class Hello
{
int n = 1;
public void sayhello(StringRepository context)
{
System.out.println("Hello, " + context.get("name");
System.out.println("visit number " + n++);
System.out.println("from IP: " + context.get("remote.address"));
System.out.println("browser: " + context.get("http.request.header.User-Agent"));
}
}
Now imagine we wish to "wrap" this content in a nice HTML web page. Here are the steps:
<html><head><title>A greeting from Mojasef</title></head> <body> <img src='http://www.stringtree.org/mojasef/images/mojasef.jpg'/> <h2>Welcome</h2> My name is Mojasef,<br/> <pre>A Gentle Ramble
This is a good place to start if you are not sure what Mojasef is, or what it has to offer. Installation, basic configuration, and some of the major features are introduced in a comfortable step-by-step approach.
ContentsHello World
- Hello World
- Counting Hits
- User Sessions
- Permanent Configurations
- Catching All Accesses
- Finding Things Out
- Passing Things Back To The Server
- Sharing Context Between Several Methods
- Making Nice Looking Pages
- Magic With Templates
In the time-honoured tradition, let's start with "hello, world". In keeping with the principles described above, we want to be able to reuse as much as possible that we have learned before. So let's start with an old "Hello.java" that we might have lying around after learning Java in the first place:
public class Hello { public void sayhello() { System.out.println("Hello, World!"); } public static void main(String[] args) { Hello hello = new Hello(); hello.sayhello(); } }To put this program on the web, we need to make the following changes to it: none at all :)
To use this program with Mojasef:
- compile Hello.java so that you get a file Hello.class in your current directory
- run Mojasef using
java -Dhttp.application$=Hello -jar moja-http.jar
- point your browser at http://localhost/sayhello
If all that went according to plan, you should see "Hello World!". That was pretty straightforward, wasn't it.
As an aside, note the "$", that little character is actually part of some key technology that gives mojasef its simple and flexible configuration power. See the manual for more details.
Counting HitsNow, this is pretty simplistic, so far. So let's tighten it up a bit. First, Our "Hello" application may only be a dozen lines, but it's still too big. So let's remove some redundant code. We don't need any of that "main" nonsense, becuse mojasef already knows how to create an object and call a method.
public class Hello { public void sayhello() { System.out.println("Hello, World!"); } }That all looks neat enough, but it doesn't actually do much so far. How about remembering things between page requests? To keep it simple for this example, we'll just make it count accesses.
public class Hello { int n = 1; public void sayhello() { System.out.println("Hello, World!"); System.out.println("visit number " + n++); } }Try it. Stop the server (using Control-C, task manager or whatever), recompile Hello.java, and run Mojasef again. Refresh your browser a few times to see that it really is counting. Cool. And way simpler than messing about with the Servlet API.
OK, those of you with servlet experience might be beginning to worry. Relying on member variables in a multi-threaded situation is extremely risky and a classic servlet problem. So this is a little simplistic as it stands. If you are getting a lot of hits and you really care about your hit count being "absolutely correct", you should probably wrap the increment in a synchronized block. On the other hand, you could sidestep the problem by reading the next section.
User SessionsCounting is good, but sometimes we don't want to share a single value. How about if we want to keep a separate count for each visitor? This might seem tricky, but not in mojasef. Just run the server using:
To test this yourself you will either need to access it from different machines, or using different browsers. Firefox and Internet Explorer, for example. Point both your browsers at the same URL and try different combinations of refreshing the page to prove to yourself that the counts really are independent.
Permanent ConfigurationsAt this point I'd be getting fed up with typing such a long command line. Luckily Mojasef allows you to store stuff like this in a file ready to be picked up when the server starts. Here's a simple way to do that:
http.application$=http.SessionWrapper Hello
java -jar moja-http.jar
While playing with this, you may have noticed that it's got a pretty narrow view of what URLs to respond to. Anything other than /sayhello gets a 404 "not found" error. There are several ways to improve this, but one of the simplest is to rename the method from sayhello() to request(). request() is one of the method names that Mojasef looks for if it can't find a match on the URL.
public class Hello
{
int n = 1;
public void request()
{
System.out.println("Hello, World!");
System.out.println("visit number " + n++);
}
}
Now our server is much more polite. It will say "hello" to all visitors, whatever URL they ask for.
As an aside, Mojasef will always look for specific names before trying more general options like request, so we can add another method:
public class Hello
{
int n = 1;
public void request()
{
System.out.println("Hello, World!");
System.out.println("visit number " + n++);
}
public void secret()
{
System.out.println("you found the hidden page :)");
}
}
Now pretty much every URL we can think of (such as http://localhost/wibble, or whatever) will give us the "Hello, World" message , but if we happen on http://localhost/secret we get the other message. Anyone for web services without all that fuss?
Finding things outPlacing our raw objects on the web is a cool thing to do, but so far, not really as useful as the ugly Servlet API. If nothing else we need to be able to look at the parameters passd to GET and POST requests, and maybe other things such as request headers, cookies, and system configurations. Luckily, Mojasef comes to our aid with this too.
There are two major ways to get access to this kind of context information. I'll break with tradition in this simple tutorial, and introduce both of them, so you can get the idea of the possibilities. For the first approach, let's go back to our basic "hello" program and add a parameter to our method:
import org.stringtree.StringFetcher;
public class Hello
{
int n = 1;
public void sayhello(StringFetcher context)
{
System.out.println("Hello, World!");
System.out.println("visit number " + n++);
}
}
As you can see, we have eventually needed to import an extra class. Don't worry, though. This class doesn't carry a lot of baggage. StringFetcher is a very simple interface consisting of two methods:
If you want to fetch something in the form of a String from the surrounding context, use get(String key). This method returns the string value of the named context object, or the empty string ("") if not present.
If you want to fetch any other type of object, use getObject(String key) and cast the result if necessary. Note that if there is nothing stored with the named key, this methjod will return null, so it can also be used to check for the presence or absence of a value.
To compile this class you will need to make sure that StringFetcher is in your compilation classpath. The simplest thing to do at the moment is to just put the whole moja-http.jar in the classpath. Later, you may prefer to use the much "lighter" stringtree-if.jar from the Stringtree project, which contains all the interfaces needed to write applications that use Mojasef, but contains no implementation code to slow down and clutter up compilation.
To compile the class either place stringtree-if.jar or moja-http.jar in your classpath or use:
javac -classpath moja-http.jar Hello.java
As an aside, when Mojasef starts up, it will look for class directories and jar files in a lib directory, so when you find that you want to use external APIs or libraries in your applications or components, just pop them in there.
Now we have a context to look in, we really ought to use this new parameter to fetch some information. let's print the originating IP address of the request and the browser identification header, and (to make our greeting more personal), the value of a supplied "name" parameter.
import org.stringtree.StringFetcher;
public class Hello
{
int n = 1;
public void sayhello(StringFetcher context)
{
System.out.println("Hello, " + context.get("name");
System.out.println("visit number " + n++);
System.out.println("from IP: " + context.get("remote.address"));
System.out.println("browser: " + context.get("http.request.header.User-Agent"));
}
}
If you pop to the browser and request your mojasef URL (probably still http://localhost/sayhello), you should see something starting with
If you want something other than an empty string to appear when no parameter is supplied, you can easily set a default value in the same place you sett http.application. Add the following line to http.spec and restart the server:
name=Mystery Guest
Now, viewing http://localhost/sayhello gives:
This is all simple enough, but the output is beginning to look a bit messy. Despite using "println" to put our text on separate lines, the web browser is treating it as HTML, and displaying it all on one line. A simple way to fix this is to set the output "content type" to text/plain to override the default. You may notice that the StringFetcher we are passing in to this method only has "get" methods, If we want to be able to "put" output values as well, we need to pass in a StringRepository instead. Let's modify our class by changing the import statement and the parameter declaration:
import org.stringtree.StringRepository;
public class Hello
{
int n = 1;
public void sayhello(StringRepository context)
{
System.out.println("Hello, " + context.get("name");
System.out.println("visit number " + n++);
System.out.println("from IP: " + context.get("remote.address"));
System.out.println("browser: " + context.get("http.request.header.User-Agent"));
}
}
If you compile and run this, you'll notice that it behaves exactly as it did before. A StringRepository is also a StringFetcher, and provides all the same methods and results. StringRepository also provides two extra methods, with pretty obvious uses:
Actually, StringRepository also provides a clear() method, but using it in a Mojasef application can be a very quick way to screw up your program unless you know exactly what you are doing.
To set the page content-type, let's add a line to put "text/plain" back into the context:
import org.stringtree.StringRepository;
public class Hello
{
int n = 1;
public void sayhello(StringRepository context)
{
System.out.println("Hello, " + context.get("name");
System.out.println("visit number " + n++);
System.out.println("from IP: " + context.get("remote.address"));
System.out.println("browser: " + context.get("http.request.header.User-Agent"));
context.put("http.response.header.Content-Type", "text/plain");
}
}
Compile the class and restart the server. You should now see your messages in glorious plain-text-o-vision.
There are, of course, pleanty of other things you can put back into the context. Anything with a name starting with http.response.header. will be sent as an outgoing header. You can set the HTTP response code (e.g. 404 for missing page) using http.response.code, and set outgoing cookies by starting names with http.response.cookie.. You can also set any other value you like, to be used in a page template, but that will be introduced in a little while, below. For more details of any of this, see the manual.
Sharing context between several methodsRemember I mentioned another way of getting information from the server? If we want to keep our methods with no parameters (this is more usual when there are several other pre-existing methods in the class which we also want to make available on the web), we can make use of another mojasef feature. Before calling any method on our object, mojasef will first try the special method warmup. Just as with request, mojasef will look for warmup(StringRepository context), warmup(StringFetcher context), or warmup(). If you provide any of those public methods, mojasef will call it, and you can keep a copy of the supplied context if you want:
import org.stringtree.StringRepository;
public class Hello
{
int n = 1;
StringRepository context;
public void warmup(StringRepository context)
{
this.context = context;
}
public void sayhello()
{
System.out.println("Hello, " + context.get("name");
context.put("http.response.header.Content-Type", "text/plain");
}
public void secret()
{
System.out.println(context,get(name) + ", you found the hidden page :)");
}
}
Making nice-looking pages
Interesting as that side-trip into sending our page as plain text was, it's not much use for the bulk of web pages. Most web pages use HTML, and use markup such as <a href> for links and <p> for paragraphs. If we want to put this sort of stuff in our pages, we've got quite a few choices. Popular approaches used with the Servlet API include:
There are probably more. Realistically, you can use any of these with Mojasef, too. Mojasef also provides a few alternative options that (I feel) integrate better with the core application. To the above choices, Mojasef adds:
To show how this works, let's go back a bit, to our application without the text/plain:
import org.stringtree.StringRepository;
public class Hello
{
int n = 1;
public void sayhello(StringRepository context)
{
System.out.println("Hello, " + context.get("name");
System.out.println("visit number " + n++);
System.out.println("from IP: " + context.get("remote.address"));
System.out.println("browser: " + context.get("http.request.header.User-Agent"));
}
}
Now imagine we wish to "wrap" this content in a nice HTML web page. Here are the steps:
<html><head><title>A greeting from Mojasef</title></head>
<body>
<img src='http://www.stringtree.org/mojasef/images/mojasef.jpg'/>
<h2>Welcome</h2>
My name is Mojasef,<br/>
<pre>${CONTENT}</pre>
</body>
</html>
You should see a decorated page with the generated content nicely laid out in a preformatted block. Try changing the template and fetch the page again. You should see it change each time. The important thing to note about this is that you do not need to change the application to use different templates.
However, that's not all. Specifying such a default template causes it to be applied to all pages generated by the application. This may be just what you want, but you may want something special for some pages. There are a few ways of changing this.
To show all these in action, let's add a few small methods to our application;
import org.stringtree.StringRepository;
public class Hello
{
int n = 1;
public void sayhello(StringRepository context)
{
System.out.println("Hello, " + context.get("name");
System.out.println("visit number " + n++);
System.out.println("from IP: " + context.get("remote.address"));
System.out.println("browser: " + context.get("http.request.header.User-Agent"));
}
public void ex1(StringRepository context)
{
System.out.println("This uses an explicit template");
context.put("page.template", "whatever");
}
public void ex2(StringRepository context)
{
System.out.println("This uses a page class");
context.put("page.class", "thing");
}
public void ex3(StringRepository context)
{
System.out.println("This uses a template with the same name");
}
}
If you compile this and restart the server, then fetch the pages (using, for example http://localhost/ex1) you will see that they all still use the same default template. Mojasef tried to find the templates they ask for, but we haven't created them yet, so it falls back to what it knows. We can add appropriate templates to the system and see them in action. I'll let you create these templates as you like. Just remember that the generated page content will appear wherever you put ${CONTENT}. Any other context value can also be displayed by placing its name between ${ and }.
If all you want to do is return some static text, or examine some context values, without even running any application code, you can do that too.
Magic with Templates
As a final twist, all the templates we have used so far are just what Mojasef considers simple templates. Templates can be made much more powerful by setting and invoking context values in the template itself, and by using the algebra of templates to process complex context values. More details can be found in the manual.
Just to show how this can be useful, try the following:
<table>
<tr><th colspan='2'>Request Context - only available during a request</th></tr>
<tr><th>Name</th><th>Value</th></tr>
${request.context*row}
<tr><th colspan='2'>System Context - available at any time</th></tr>
<tr><th>Name</th><th>Value</th></tr>
${system.context*row}
</table>
<tr><td>${CONTENT}</td><td>${^CONTENT}</td></tr>
That's the end of this simple demonstration. For more information about what else you can do with Mojasef, you can either check out the next example Making a Quiz, or head straight for the manual. Have Fun!
</pre> </body> </html>You should see a decorated page with the generated content nicely laid out in a preformatted block. Try changing the template and fetch the page again. You should see it change each time. The important thing to note about this is that you do not need to change the application to use different templates.
However, that's not all. Specifying such a default template causes it to be applied to all pages generated by the application. This may be just what you want, but you may want something special for some pages. There are a few ways of changing this.
To show all these in action, let's add a few small methods to our application;
import org.stringtree.StringRepository;
public class Hello
{
int n = 1;
public void sayhello(StringRepository context)
{
System.out.println("Hello, " + context.get("name");
System.out.println("visit number " + n++);
System.out.println("from IP: " + context.get("remote.address"));
System.out.println("browser: " + context.get("http.request.header.User-Agent"));
}
public void ex1(StringRepository context)
{
System.out.println("This uses an explicit template");
context.put("page.template", "whatever");
}
public void ex2(StringRepository context)
{
System.out.println("This uses a page class");
context.put("page.class", "thing");
}
public void ex3(StringRepository context)
{
System.out.println("This uses a template with the same name");
}
}
If you compile this and restart the server, then fetch the pages (using, for example http://localhost/ex1)
you will see that they all still use the same default template. Mojasef tried to find the templates they ask for, but we
haven't created them yet, so it falls back to what it knows. We can add appropriate templates to the system and see them in action. I'll let you create
these templates as you like. Just remember that the generated page content will appear wherever you put
A Gentle Ramble
This is a good place to start if you are not sure what Mojasef is, or what it has to offer. Installation, basic configuration, and some of the major features are introduced in a comfortable step-by-step approach.
ContentsIn the time-honoured tradition, let's start with "hello, world". In keeping with the principles described above, we want to be able to reuse as much as possible that we have learned before. So let's start with an old "Hello.java" that we might have lying around after learning Java in the first place:
public class Hello
{
public void sayhello()
{
System.out.println("Hello, World!");
}
public static void main(String[] args)
{
Hello hello = new Hello();
hello.sayhello();
}
}
To put this program on the web, we need to make the following changes to it: none at all :)
To use this program with Mojasef:
java -Dhttp.application$=Hello -jar moja-http.jar
If all that went according to plan, you should see "Hello World!". That was pretty straightforward, wasn't it.
As an aside, note the "$", that little character is actually part of some key technology that gives mojasef its simple and flexible configuration power. See the manual for more details.
Counting HitsNow, this is pretty simplistic, so far. So let's tighten it up a bit. First, Our "Hello" application may only be a dozen lines, but it's still too big. So let's remove some redundant code. We don't need any of that "main" nonsense, becuse mojasef already knows how to create an object and call a method.
public class Hello
{
public void sayhello()
{
System.out.println("Hello, World!");
}
}
That all looks neat enough, but it doesn't actually do much so far. How about remembering things between page requests? To keep it simple for this example, we'll just make it count accesses.
public class Hello
{
int n = 1;
public void sayhello()
{
System.out.println("Hello, World!");
System.out.println("visit number " + n++);
}
}
Try it. Stop the server (using Control-C, task manager or whatever), recompile Hello.java, and run Mojasef again. Refresh your browser a few times to see that it really is counting. Cool. And way simpler than messing about with the Servlet API.
OK, those of you with servlet experience might be beginning to worry. Relying on member variables in a multi-threaded situation is extremely risky and a classic servlet problem. So this is a little simplistic as it stands. If you are getting a lot of hits and you really care about your hit count being "absolutely correct", you should probably wrap the increment in a synchronized block. On the other hand, you could sidestep the problem by reading the next section.
User SessionsCounting is good, but sometimes we don't want to share a single value. How about if we want to keep a separate count for each visitor? This might seem tricky, but not in mojasef. Just run the server using:
To test this yourself you will either need to access it from different machines, or using different browsers. Firefox and Internet Explorer, for example. Point both your browsers at the same URL and try different combinations of refreshing the page to prove to yourself that the counts really are independent.
Permanent ConfigurationsAt this point I'd be getting fed up with typing such a long command line. Luckily Mojasef allows you to store stuff like this in a file ready to be picked up when the server starts. Here's a simple way to do that:
http.application$=http.SessionWrapper Hello
java -jar moja-http.jar
While playing with this, you may have noticed that it's got a pretty narrow view of what URLs to respond to. Anything other than /sayhello gets a 404 "not found" error. There are several ways to improve this, but one of the simplest is to rename the method from sayhello() to request(). request() is one of the method names that Mojasef looks for if it can't find a match on the URL.
public class Hello
{
int n = 1;
public void request()
{
System.out.println("Hello, World!");
System.out.println("visit number " + n++);
}
}
Now our server is much more polite. It will say "hello" to all visitors, whatever URL they ask for.
As an aside, Mojasef will always look for specific names before trying more general options like request, so we can add another method:
public class Hello
{
int n = 1;
public void request()
{
System.out.println("Hello, World!");
System.out.println("visit number " + n++);
}
public void secret()
{
System.out.println("you found the hidden page :)");
}
}
Now pretty much every URL we can think of (such as http://localhost/wibble, or whatever) will give us the "Hello, World" message , but if we happen on http://localhost/secret we get the other message. Anyone for web services without all that fuss?
Finding things outPlacing our raw objects on the web is a cool thing to do, but so far, not really as useful as the ugly Servlet API. If nothing else we need to be able to look at the parameters passd to GET and POST requests, and maybe other things such as request headers, cookies, and system configurations. Luckily, Mojasef comes to our aid with this too.
There are two major ways to get access to this kind of context information. I'll break with tradition in this simple tutorial, and introduce both of them, so you can get the idea of the possibilities. For the first approach, let's go back to our basic "hello" program and add a parameter to our method:
import org.stringtree.StringFetcher;
public class Hello
{
int n = 1;
public void sayhello(StringFetcher context)
{
System.out.println("Hello, World!");
System.out.println("visit number " + n++);
}
}
As you can see, we have eventually needed to import an extra class. Don't worry, though. This class doesn't carry a lot of baggage. StringFetcher is a very simple interface consisting of two methods:
If you want to fetch something in the form of a String from the surrounding context, use get(String key). This method returns the string value of the named context object, or the empty string ("") if not present.
If you want to fetch any other type of object, use getObject(String key) and cast the result if necessary. Note that if there is nothing stored with the named key, this methjod will return null, so it can also be used to check for the presence or absence of a value.
To compile this class you will need to make sure that StringFetcher is in your compilation classpath. The simplest thing to do at the moment is to just put the whole moja-http.jar in the classpath. Later, you may prefer to use the much "lighter" stringtree-if.jar from the Stringtree project, which contains all the interfaces needed to write applications that use Mojasef, but contains no implementation code to slow down and clutter up compilation.
To compile the class either place stringtree-if.jar or moja-http.jar in your classpath or use:
javac -classpath moja-http.jar Hello.java
As an aside, when Mojasef starts up, it will look for class directories and jar files in a lib directory, so when you find that you want to use external APIs or libraries in your applications or components, just pop them in there.
Now we have a context to look in, we really ought to use this new parameter to fetch some information. let's print the originating IP address of the request and the browser identification header, and (to make our greeting more personal), the value of a supplied "name" parameter.
import org.stringtree.StringFetcher;
public class Hello
{
int n = 1;
public void sayhello(StringFetcher context)
{
System.out.println("Hello, " + context.get("name");
System.out.println("visit number " + n++);
System.out.println("from IP: " + context.get("remote.address"));
System.out.println("browser: " + context.get("http.request.header.User-Agent"));
}
}
If you pop to the browser and request your mojasef URL (probably still http://localhost/sayhello), you should see something starting with
If you want something other than an empty string to appear when no parameter is supplied, you can easily set a default value in the same place you sett http.application. Add the following line to http.spec and restart the server:
name=Mystery Guest
Now, viewing http://localhost/sayhello gives:
This is all simple enough, but the output is beginning to look a bit messy. Despite using "println" to put our text on separate lines, the web browser is treating it as HTML, and displaying it all on one line. A simple way to fix this is to set the output "content type" to text/plain to override the default. You may notice that the StringFetcher we are passing in to this method only has "get" methods, If we want to be able to "put" output values as well, we need to pass in a StringRepository instead. Let's modify our class by changing the import statement and the parameter declaration:
import org.stringtree.StringRepository;
public class Hello
{
int n = 1;
public void sayhello(StringRepository context)
{
System.out.println("Hello, " + context.get("name");
System.out.println("visit number " + n++);
System.out.println("from IP: " + context.get("remote.address"));
System.out.println("browser: " + context.get("http.request.header.User-Agent"));
}
}
If you compile and run this, you'll notice that it behaves exactly as it did before. A StringRepository is also a StringFetcher, and provides all the same methods and results. StringRepository also provides two extra methods, with pretty obvious uses:
Actually, StringRepository also provides a clear() method, but using it in a Mojasef application can be a very quick way to screw up your program unless you know exactly what you are doing.
To set the page content-type, let's add a line to put "text/plain" back into the context:
import org.stringtree.StringRepository;
public class Hello
{
int n = 1;
public void sayhello(StringRepository context)
{
System.out.println("Hello, " + context.get("name");
System.out.println("visit number " + n++);
System.out.println("from IP: " + context.get("remote.address"));
System.out.println("browser: " + context.get("http.request.header.User-Agent"));
context.put("http.response.header.Content-Type", "text/plain");
}
}
Compile the class and restart the server. You should now see your messages in glorious plain-text-o-vision.
There are, of course, pleanty of other things you can put back into the context. Anything with a name starting with http.response.header. will be sent as an outgoing header. You can set the HTTP response code (e.g. 404 for missing page) using http.response.code, and set outgoing cookies by starting names with http.response.cookie.. You can also set any other value you like, to be used in a page template, but that will be introduced in a little while, below. For more details of any of this, see the manual.
Sharing context between several methodsRemember I mentioned another way of getting information from the server? If we want to keep our methods with no parameters (this is more usual when there are several other pre-existing methods in the class which we also want to make available on the web), we can make use of another mojasef feature. Before calling any method on our object, mojasef will first try the special method warmup. Just as with request, mojasef will look for warmup(StringRepository context), warmup(StringFetcher context), or warmup(). If you provide any of those public methods, mojasef will call it, and you can keep a copy of the supplied context if you want:
import org.stringtree.StringRepository;
public class Hello
{
int n = 1;
StringRepository context;
public void warmup(StringRepository context)
{
this.context = context;
}
public void sayhello()
{
System.out.println("Hello, " + context.get("name");
context.put("http.response.header.Content-Type", "text/plain");
}
public void secret()
{
System.out.println(context,get(name) + ", you found the hidden page :)");
}
}
Making nice-looking pages
Interesting as that side-trip into sending our page as plain text was, it's not much use for the bulk of web pages. Most web pages use HTML, and use markup such as <a href> for links and <p> for paragraphs. If we want to put this sort of stuff in our pages, we've got quite a few choices. Popular approaches used with the Servlet API include:
There are probably more. Realistically, you can use any of these with Mojasef, too. Mojasef also provides a few alternative options that (I feel) integrate better with the core application. To the above choices, Mojasef adds:
To show how this works, let's go back a bit, to our application without the text/plain:
import org.stringtree.StringRepository;
public class Hello
{
int n = 1;
public void sayhello(StringRepository context)
{
System.out.println("Hello, " + context.get("name");
System.out.println("visit number " + n++);
System.out.println("from IP: " + context.get("remote.address"));
System.out.println("browser: " + context.get("http.request.header.User-Agent"));
}
}
Now imagine we wish to "wrap" this content in a nice HTML web page. Here are the steps:
<html><head><title>A greeting from Mojasef</title></head>
<body>
<img src='http://www.stringtree.org/mojasef/images/mojasef.jpg'/>
<h2>Welcome</h2>
My name is Mojasef,<br/>
<pre>${CONTENT}</pre>
</body>
</html>
You should see a decorated page with the generated content nicely laid out in a preformatted block. Try changing the template and fetch the page again. You should see it change each time. The important thing to note about this is that you do not need to change the application to use different templates.
However, that's not all. Specifying such a default template causes it to be applied to all pages generated by the application. This may be just what you want, but you may want something special for some pages. There are a few ways of changing this.
To show all these in action, let's add a few small methods to our application;
import org.stringtree.StringRepository;
public class Hello
{
int n = 1;
public void sayhello(StringRepository context)
{
System.out.println("Hello, " + context.get("name");
System.out.println("visit number " + n++);
System.out.println("from IP: " + context.get("remote.address"));
System.out.println("browser: " + context.get("http.request.header.User-Agent"));
}
public void ex1(StringRepository context)
{
System.out.println("This uses an explicit template");
context.put("page.template", "whatever");
}
public void ex2(StringRepository context)
{
System.out.println("This uses a page class");
context.put("page.class", "thing");
}
public void ex3(StringRepository context)
{
System.out.println("This uses a template with the same name");
}
}
If you compile this and restart the server, then fetch the pages (using, for example http://localhost/ex1) you will see that they all still use the same default template. Mojasef tried to find the templates they ask for, but we haven't created them yet, so it falls back to what it knows. We can add appropriate templates to the system and see them in action. I'll let you create these templates as you like. Just remember that the generated page content will appear wherever you put ${CONTENT}. Any other context value can also be displayed by placing its name between ${ and }.
If all you want to do is return some static text, or examine some context values, without even running any application code, you can do that too.
Magic with Templates
As a final twist, all the templates we have used so far are just what Mojasef considers simple templates. Templates can be made much more powerful by setting and invoking context values in the template itself, and by using the algebra of templates to process complex context values. More details can be found in the manual.
Just to show how this can be useful, try the following:
<table>
<tr><th colspan='2'>Request Context - only available during a request</th></tr>
<tr><th>Name</th><th>Value</th></tr>
${request.context*row}
<tr><th colspan='2'>System Context - available at any time</th></tr>
<tr><th>Name</th><th>Value</th></tr>
${system.context*row}
</table>
<tr><td>${CONTENT}</td><td>${^CONTENT}</td></tr>
That's the end of this simple demonstration. For more information about what else you can do with Mojasef, you can either check out the next example Making a Quiz, or head straight for the manual. Have Fun!
. Any other context value can also be displayed by placing its name between .If all you want to do is return some static text, or examine some context values, without even running any application code, you can do that too.
Magic with Templates
As a final twist, all the templates we have used so far are just what Mojasef considers simple templates. Templates can be made much more powerful by setting and invoking context values in the template itself, and by using the algebra of templates to process complex context values. More details can be found in the manual.
Just to show how this can be useful, try the following:
<table> <tr><th colspan='2'>Request Context - only available during a request</th></tr> <tr><th>Name</th><th>Value</th></tr> <tr><th colspan='2'>System Context - available at any time</th></tr> <tr><th>Name</th><th>Value</th></tr> </table>
<tr><td>A Gentle Ramble
This is a good place to start if you are not sure what Mojasef is, or what it has to offer. Installation, basic configuration, and some of the major features are introduced in a comfortable step-by-step approach.
ContentsHello World
- Hello World
- Counting Hits
- User Sessions
- Permanent Configurations
- Catching All Accesses
- Finding Things Out
- Passing Things Back To The Server
- Sharing Context Between Several Methods
- Making Nice Looking Pages
- Magic With Templates
In the time-honoured tradition, let's start with "hello, world". In keeping with the principles described above, we want to be able to reuse as much as possible that we have learned before. So let's start with an old "Hello.java" that we might have lying around after learning Java in the first place:
public class Hello { public void sayhello() { System.out.println("Hello, World!"); } public static void main(String[] args) { Hello hello = new Hello(); hello.sayhello(); } }To put this program on the web, we need to make the following changes to it: none at all :)
To use this program with Mojasef:
- compile Hello.java so that you get a file Hello.class in your current directory
- run Mojasef using
java -Dhttp.application$=Hello -jar moja-http.jar
- point your browser at http://localhost/sayhello
If all that went according to plan, you should see "Hello World!". That was pretty straightforward, wasn't it.
As an aside, note the "$", that little character is actually part of some key technology that gives mojasef its simple and flexible configuration power. See the manual for more details.
Counting HitsNow, this is pretty simplistic, so far. So let's tighten it up a bit. First, Our "Hello" application may only be a dozen lines, but it's still too big. So let's remove some redundant code. We don't need any of that "main" nonsense, becuse mojasef already knows how to create an object and call a method.
public class Hello { public void sayhello() { System.out.println("Hello, World!"); } }That all looks neat enough, but it doesn't actually do much so far. How about remembering things between page requests? To keep it simple for this example, we'll just make it count accesses.
public class Hello { int n = 1; public void sayhello() { System.out.println("Hello, World!"); System.out.println("visit number " + n++); } }Try it. Stop the server (using Control-C, task manager or whatever), recompile Hello.java, and run Mojasef again. Refresh your browser a few times to see that it really is counting. Cool. And way simpler than messing about with the Servlet API.
OK, those of you with servlet experience might be beginning to worry. Relying on member variables in a multi-threaded situation is extremely risky and a classic servlet problem. So this is a little simplistic as it stands. If you are getting a lot of hits and you really care about your hit count being "absolutely correct", you should probably wrap the increment in a synchronized block. On the other hand, you could sidestep the problem by reading the next section.
User SessionsCounting is good, but sometimes we don't want to share a single value. How about if we want to keep a separate count for each visitor? This might seem tricky, but not in mojasef. Just run the server using:
To test this yourself you will either need to access it from different machines, or using different browsers. Firefox and Internet Explorer, for example. Point both your browsers at the same URL and try different combinations of refreshing the page to prove to yourself that the counts really are independent.
Permanent ConfigurationsAt this point I'd be getting fed up with typing such a long command line. Luckily Mojasef allows you to store stuff like this in a file ready to be picked up when the server starts. Here's a simple way to do that:
http.application$=http.SessionWrapper Hello
java -jar moja-http.jar
While playing with this, you may have noticed that it's got a pretty narrow view of what URLs to respond to. Anything other than /sayhello gets a 404 "not found" error. There are several ways to improve this, but one of the simplest is to rename the method from sayhello() to request(). request() is one of the method names that Mojasef looks for if it can't find a match on the URL.
public class Hello
{
int n = 1;
public void request()
{
System.out.println("Hello, World!");
System.out.println("visit number " + n++);
}
}
Now our server is much more polite. It will say "hello" to all visitors, whatever URL they ask for.
As an aside, Mojasef will always look for specific names before trying more general options like request, so we can add another method:
public class Hello
{
int n = 1;
public void request()
{
System.out.println("Hello, World!");
System.out.println("visit number " + n++);
}
public void secret()
{
System.out.println("you found the hidden page :)");
}
}
Now pretty much every URL we can think of (such as http://localhost/wibble, or whatever) will give us the "Hello, World" message , but if we happen on http://localhost/secret we get the other message. Anyone for web services without all that fuss?
Finding things outPlacing our raw objects on the web is a cool thing to do, but so far, not really as useful as the ugly Servlet API. If nothing else we need to be able to look at the parameters passd to GET and POST requests, and maybe other things such as request headers, cookies, and system configurations. Luckily, Mojasef comes to our aid with this too.
There are two major ways to get access to this kind of context information. I'll break with tradition in this simple tutorial, and introduce both of them, so you can get the idea of the possibilities. For the first approach, let's go back to our basic "hello" program and add a parameter to our method:
import org.stringtree.StringFetcher;
public class Hello
{
int n = 1;
public void sayhello(StringFetcher context)
{
System.out.println("Hello, World!");
System.out.println("visit number " + n++);
}
}
As you can see, we have eventually needed to import an extra class. Don't worry, though. This class doesn't carry a lot of baggage. StringFetcher is a very simple interface consisting of two methods:
If you want to fetch something in the form of a String from the surrounding context, use get(String key). This method returns the string value of the named context object, or the empty string ("") if not present.
If you want to fetch any other type of object, use getObject(String key) and cast the result if necessary. Note that if there is nothing stored with the named key, this methjod will return null, so it can also be used to check for the presence or absence of a value.
To compile this class you will need to make sure that StringFetcher is in your compilation classpath. The simplest thing to do at the moment is to just put the whole moja-http.jar in the classpath. Later, you may prefer to use the much "lighter" stringtree-if.jar from the Stringtree project, which contains all the interfaces needed to write applications that use Mojasef, but contains no implementation code to slow down and clutter up compilation.
To compile the class either place stringtree-if.jar or moja-http.jar in your classpath or use:
javac -classpath moja-http.jar Hello.java
As an aside, when Mojasef starts up, it will look for class directories and jar files in a lib directory, so when you find that you want to use external APIs or libraries in your applications or components, just pop them in there.
Now we have a context to look in, we really ought to use this new parameter to fetch some information. let's print the originating IP address of the request and the browser identification header, and (to make our greeting more personal), the value of a supplied "name" parameter.
import org.stringtree.StringFetcher;
public class Hello
{
int n = 1;
public void sayhello(StringFetcher context)
{
System.out.println("Hello, " + context.get("name");
System.out.println("visit number " + n++);
System.out.println("from IP: " + context.get("remote.address"));
System.out.println("browser: " + context.get("http.request.header.User-Agent"));
}
}
If you pop to the browser and request your mojasef URL (probably still http://localhost/sayhello), you should see something starting with
If you want something other than an empty string to appear when no parameter is supplied, you can easily set a default value in the same place you sett http.application. Add the following line to http.spec and restart the server:
name=Mystery Guest
Now, viewing http://localhost/sayhello gives:
This is all simple enough, but the output is beginning to look a bit messy. Despite using "println" to put our text on separate lines, the web browser is treating it as HTML, and displaying it all on one line. A simple way to fix this is to set the output "content type" to text/plain to override the default. You may notice that the StringFetcher we are passing in to this method only has "get" methods, If we want to be able to "put" output values as well, we need to pass in a StringRepository instead. Let's modify our class by changing the import statement and the parameter declaration:
import org.stringtree.StringRepository;
public class Hello
{
int n = 1;
public void sayhello(StringRepository context)
{
System.out.println("Hello, " + context.get("name");
System.out.println("visit number " + n++);
System.out.println("from IP: " + context.get("remote.address"));
System.out.println("browser: " + context.get("http.request.header.User-Agent"));
}
}
If you compile and run this, you'll notice that it behaves exactly as it did before. A StringRepository is also a StringFetcher, and provides all the same methods and results. StringRepository also provides two extra methods, with pretty obvious uses:
Actually, StringRepository also provides a clear() method, but using it in a Mojasef application can be a very quick way to screw up your program unless you know exactly what you are doing.
To set the page content-type, let's add a line to put "text/plain" back into the context:
import org.stringtree.StringRepository;
public class Hello
{
int n = 1;
public void sayhello(StringRepository context)
{
System.out.println("Hello, " + context.get("name");
System.out.println("visit number " + n++);
System.out.println("from IP: " + context.get("remote.address"));
System.out.println("browser: " + context.get("http.request.header.User-Agent"));
context.put("http.response.header.Content-Type", "text/plain");
}
}
Compile the class and restart the server. You should now see your messages in glorious plain-text-o-vision.
There are, of course, pleanty of other things you can put back into the context. Anything with a name starting with http.response.header. will be sent as an outgoing header. You can set the HTTP response code (e.g. 404 for missing page) using http.response.code, and set outgoing cookies by starting names with http.response.cookie.. You can also set any other value you like, to be used in a page template, but that will be introduced in a little while, below. For more details of any of this, see the manual.
Sharing context between several methodsRemember I mentioned another way of getting information from the server? If we want to keep our methods with no parameters (this is more usual when there are several other pre-existing methods in the class which we also want to make available on the web), we can make use of another mojasef feature. Before calling any method on our object, mojasef will first try the special method warmup. Just as with request, mojasef will look for warmup(StringRepository context), warmup(StringFetcher context), or warmup(). If you provide any of those public methods, mojasef will call it, and you can keep a copy of the supplied context if you want:
import org.stringtree.StringRepository;
public class Hello
{
int n = 1;
StringRepository context;
public void warmup(StringRepository context)
{
this.context = context;
}
public void sayhello()
{
System.out.println("Hello, " + context.get("name");
context.put("http.response.header.Content-Type", "text/plain");
}
public void secret()
{
System.out.println(context,get(name) + ", you found the hidden page :)");
}
}
Making nice-looking pages
Interesting as that side-trip into sending our page as plain text was, it's not much use for the bulk of web pages. Most web pages use HTML, and use markup such as <a href> for links and <p> for paragraphs. If we want to put this sort of stuff in our pages, we've got quite a few choices. Popular approaches used with the Servlet API include:
There are probably more. Realistically, you can use any of these with Mojasef, too. Mojasef also provides a few alternative options that (I feel) integrate better with the core application. To the above choices, Mojasef adds:
To show how this works, let's go back a bit, to our application without the text/plain:
import org.stringtree.StringRepository;
public class Hello
{
int n = 1;
public void sayhello(StringRepository context)
{
System.out.println("Hello, " + context.get("name");
System.out.println("visit number " + n++);
System.out.println("from IP: " + context.get("remote.address"));
System.out.println("browser: " + context.get("http.request.header.User-Agent"));
}
}
Now imagine we wish to "wrap" this content in a nice HTML web page. Here are the steps:
<html><head><title>A greeting from Mojasef</title></head>
<body>
<img src='http://www.stringtree.org/mojasef/images/mojasef.jpg'/>
<h2>Welcome</h2>
My name is Mojasef,<br/>
<pre>${CONTENT}</pre>
</body>
</html>
You should see a decorated page with the generated content nicely laid out in a preformatted block. Try changing the template and fetch the page again. You should see it change each time. The important thing to note about this is that you do not need to change the application to use different templates.
However, that's not all. Specifying such a default template causes it to be applied to all pages generated by the application. This may be just what you want, but you may want something special for some pages. There are a few ways of changing this.
To show all these in action, let's add a few small methods to our application;
import org.stringtree.StringRepository;
public class Hello
{
int n = 1;
public void sayhello(StringRepository context)
{
System.out.println("Hello, " + context.get("name");
System.out.println("visit number " + n++);
System.out.println("from IP: " + context.get("remote.address"));
System.out.println("browser: " + context.get("http.request.header.User-Agent"));
}
public void ex1(StringRepository context)
{
System.out.println("This uses an explicit template");
context.put("page.template", "whatever");
}
public void ex2(StringRepository context)
{
System.out.println("This uses a page class");
context.put("page.class", "thing");
}
public void ex3(StringRepository context)
{
System.out.println("This uses a template with the same name");
}
}
If you compile this and restart the server, then fetch the pages (using, for example http://localhost/ex1) you will see that they all still use the same default template. Mojasef tried to find the templates they ask for, but we haven't created them yet, so it falls back to what it knows. We can add appropriate templates to the system and see them in action. I'll let you create these templates as you like. Just remember that the generated page content will appear wherever you put ${CONTENT}. Any other context value can also be displayed by placing its name between ${ and }.
If all you want to do is return some static text, or examine some context values, without even running any application code, you can do that too.
Magic with Templates
As a final twist, all the templates we have used so far are just what Mojasef considers simple templates. Templates can be made much more powerful by setting and invoking context values in the template itself, and by using the algebra of templates to process complex context values. More details can be found in the manual.
Just to show how this can be useful, try the following:
<table>
<tr><th colspan='2'>Request Context - only available during a request</th></tr>
<tr><th>Name</th><th>Value</th></tr>
${request.context*row}
<tr><th colspan='2'>System Context - available at any time</th></tr>
<tr><th>Name</th><th>Value</th></tr>
${system.context*row}
</table>
<tr><td>${CONTENT}</td><td>${^CONTENT}</td></tr>
That's the end of this simple demonstration. For more information about what else you can do with Mojasef, you can either check out the next example Making a Quiz, or head straight for the manual. Have Fun!
</td><td></td></tr>That's the end of this simple demonstration. For more information about what else you can do with Mojasef, you can either check out the next example Making a Quiz, or head straight for the manual. Have Fun!