The SSIFrame (org.w3c.jigsaw.ssi.SSIFrame) is a Jigsaw frame that provides a flexible way of generating part of the content of a document from individual pieces. This may sound too general, and that's because there is little constraint on the way the constituent pieces are generated. For example, one use of the SSIFrame is the traditional one: the content of any resource can be embedded within any document exported by the SSIFrame, by using the include command from the default command registry. Some other of the default commands allow you to include the size of the document, the time of day, the hit count, and other general data.
One of the goals of this tutorial is to show that the SSIFrame is useful beyond its traditional use, as a powerful way of creating documents with a dynamically generated content. It is assumed that you are familiar with the administration of Jigsaw in general.
Upon finding a command, the SSIFrame will look it up in an object called the command registry. The command registry returns the command that is registered by that name. Then, it will call the command's execute method with the specified parameters, and with other contextual data.
Command registries are objects of class org.w3c.jigsaw.ssi.commands.CommandRegistry. Since this is an abstract class, a concrete implementation of one must be available for SSIFrame to work. One such implementation is supplied with the distribution: it is org.w3c.jigsaw.ssi.commands.DefaultCommandRegistry, which includes the bread-and-butter SSI commands. Commands are implementations of the org.w3c.jigsaw.ssi.commands.Command interface or org.w3c.jigsaw.ssi.commands.ControlCommand. The SSIFrame declares a registryClass attribute, which is set to the particular command registry to use in parsing a given document.
Therefore, the way to extend the SSIFrame is to create (either from scratch or by subclassing an existing one) a command registry that knows about the new commands that are being added. A good way to become familiar with these classes is too look at the code for DefaultCommandRegistry and its superclass, BasicCommandRegistry, and at the code for the default commands (in rough order of complexity): SampleCommand, CountCommand, ConfigCommand, FSizeCommand, FLastModCommand, EchoCommand, IncludeCommand, jdbcCommand, CounterCommand, ServletCommand.
SSIFrame allows you to create control commands like loop and test. These commands implements the org.w3c.jigsaw.ssi.commands.ControlCommand interface. Here is the code of the default control commands : IfCommand, ElseCommand, EndifCommand, LoopCommand, ExitloopCommand, EndloopCommand. The org.w3c.jigsaw.ssi.commands.Command interface has been modified, a new method was added (getValue). This method is used by some control commands (if) to get some value relative to the command.
Let's have a look of what can be done with control commands :
This shtml page display the content of the users database.
<html> <head> <title>Database SSI</title> </head> <body> <h1>Database SSI</h1> <p>This Server Side Include extension allows you to query a database, to make some loop and some tests. (which I am doing right now) <!--#jdbc select="SELECT * FROM users" name="result" driver="COM.imaginary.sql.msql.MsqlDriver" url="jdbc:msql://www43.inria.fr:4333/users" --> <p>The query has run, here is all the results:<p> <table border=2> <tr><td><b>Name</td><td><b>Login</td> <td><b>Email</td><td><b>Age</td></tr> <!--#loop name="loop1" --> <!--#jdbc name="result" next="true" --> <!--#if name="if1" command="jdbc" var="result" equals="empty" --> <!--#exitloop name="loop1" --> <!--#endif name="if1" --> <!-- the three lines above can be changed in : --> <!--#exitloop name="loop1" command="jdbc" var="result" equals="empty" --> <tr><td> <!--#jdbc name="result" column="1" --> </td><td> <!--#jdbc name="result" column="2" --> </td><td> <!--#jdbc name="result" column="3" --> </td><td> <!--#jdbc name="result" column="4" --> </td></tr> <!--#endloop name="loop1" --> </table> <hr> </body> </html>
package org.w3c.jigsaw.ssi.commands; import java.util.*; import org.w3c.jigsaw.http.* ; import org.w3c.www.http.* ; import org.w3c.tools.resources.* ; import org.w3c.util.* ; import org.w3c.jigsaw.ssi.*; /** * Implementation of the SSI if command. * @author Benoit Mahe :[email protected] */ public class IfCommand implements ControlCommand { private final static String NAME = "if"; private final static boolean debug = true; // The parameters accepted by the if command private static final String keys[] = { "name", "command", "var", "equals" }; // Used to store the position of each if command protected static Hashtable ifstore = null; static { ifstore = new Hashtable(23); } /** * Returns the (String) value of the given variable. * @return a String instance. */ public String getValue(Dictionary variables, String var) { return null; } protected static int getPosition(String name) throws ControlCommandException { Integer pos = (Integer)ifstore.get(name); if (pos == null) throw new ControlCommandException(NAME,"Position unknown."); else return pos.intValue(); } /** * register the command position in the structure * witch store the SSIFrame. */ public void setPosition(SSIFrame ssiframe, Request request, CommandRegistry registry, ArrayDictionary parameters, Dictionary variables, int position) { Object values[] = parameters.getMany(keys); String name = (String) values[0]; if (name != null) ifstore.put(ssiframe.getResource().getURLPath()+":"+name, new Integer(position)); } /** * Executes this command. Might modify variables. * Must not modify the parameters. * It may handle conditional requests, except that if * it replies with a status of HTTP.NOT_MODIFIED, it must * still reply with a content (the same content that it would have * returned for an inconditional request). This is because * further SSI commands down the line may decide thay they have * been modified, and then a content must be emitted by SSIFrame. * @param request the original HTTP request * @param parameters The parameters for this command * @param variables The global variables for the parse * @return a Reply with the output from the command */ public Reply execute(SSIFrame ssiframe, Request request, ArrayDictionary parameters, Dictionary variables) { // Empty reply return ssiframe.createCommandReply(request, HTTP.OK); } protected boolean check(CommandRegistry registry, Request request, ArrayDictionary parameters, Dictionary variables) { Object values[] = parameters.getMany(keys); String name = (String) values[0]; String command = (String) values[1]; String var = (String) values[2]; String equals = (String) values[3]; if ((command == null) || (var == null) || (equals == null)) return false; Command cmd = registry.lookupCommand(command); String value = cmd.getValue(variables,var); // here is the test return value.equals(equals); } /** * Give the next position in the structure witch * store the SSIFrame. */ public int jumpTo(SSIFrame frame, Request request, CommandRegistry registry, ArrayDictionary parameters, Dictionary variables) throws ControlCommandException { Object values[] = parameters.getMany(keys); String name = (String) values[0]; if (name != null) { if (check(registry,parameters,variables)) return getPosition(frame.getURLPath()+":"+name)+1; try { return (ElseCommand.getPosition(frame.getURLPath()+":"+name)+1); } catch (ControlCommandException ex) { return (EndifCommand.getPosition(frame.getURLPath()+":"+name)+1); } } throw new ControlCommandException(NAME,"name not initialized."); } /** * Returns the name of this command. (Case sensitivity is up to * the lookupCommand method in the command registry.) * @return the name of the command * @see org.w3c.jigsaw.ssi.commands.CommandRegistry#lookupCommand */ public String getName() { return NAME; } }With this in mind, let's implement a useful extension of SSIFrame.
package org.w3c.jigsaw.tutorials ; import java.util.* ; import org.w3c.jigsaw.http.* ; import org.w3c.www.http.HTTP ; import org.w3c.jigsaw.ssi.* ; import org.w3c.util.* ; import org.w3c.jigsaw.ssi.commands.* ; public class StatCommand implements Command { private static final String NAME = "stat" ; public final String getName() { return NAME ; } // Unuseful here public String getValue(Dictionary variables, String variable) { return null; } public Reply execute(SSIFrame frame, Request request, ArrayDictionary parameters, Dictionary variables) { // Obtain the statistics from the server httpdStatistics stats = frame.getServer().getStatistics() ; // Get the parameter specifying the kind of statistic to emit. String data = (String) parameters.get("data") ; // If the parameter is not supplied, do nothing if(data == null) return null ; // Otherwise, compare it against the possible different keywords // (Since there are no "pointers to methods", this is the simplest way it // can be written) long result = -1 ; String urlResult = null ; if(data.equalsIgnoreCase("serverload")) { result = stats.getServerLoad() ; } else if(data.equalsIgnoreCase("freethreads")) { result = stats.getFreeThreadCount() ; } else if(data.equalsIgnoreCase("idlethreads")) { result = stats.getIdleThreadCount() ; } else if(data.equalsIgnoreCase("totalthreads")) { result = stats.getTotalThreadCount() ; } else if(data.equalsIgnoreCase("hitcount")) { result = stats.getHitCount() ; } else if(data.equalsIgnoreCase("meanreqtime")) { result = stats.getMeanRequestTime() ; } else if(data.equalsIgnoreCase("maxreqtime")) { result = stats.getMaxRequestTime() ; } else if(data.equalsIgnoreCase("maxrequrl")) { urlResult = stats.getMaxRequestURL().toExternalForm() ; } else if(data.equalsIgnoreCase("minreqtime")) { result = stats.getMinRequestTime() ; } else if(data.equalsIgnoreCase("minrequrl")) { urlResult = stats.getMinRequestURL().toExternalForm() ; } else if(data.equalsIgnoreCase("emittedbytes")) { result = stats.getEmittedBytes() ; } else return null ; // Make a reply with the datum and return it Reply reply = frame.createCommandReply(request, HTTP.OK) ; reply.setContent( urlResult == null ? Long.toString(result) : urlResult ) ; return reply ; } } |
Now that the command itself is finished, we need to make it part of a command registry, so that it can be actually used in documents.
package org.w3c.jigsaw.tutorials ; import org.w3c.jigsaw.ssi.* ; import org.w3c.jigsaw.ssi.commands.* ; public class MyCommandRegistry extends DefaultCommandRegistry { public MyCommandRegistry() { registerCommand(new StatCommand()) ; } } |
We're now ready to use this command in a future document.
<!DOCTYPE HTML PUBLIC "-//IETF//DTD HTML//EN"> <html> <head> <meta http-equiv="Refresh" content="5"> <title>Server Statistics</title> </head> <body> <ul> <li>hits: <!--#stat data=hitCount --> <li>bytes: <!--#stat data=emittedBytes --> </ul> <p>Request processing times: <table border> <tr> <th align="center"> min <th align="center"> avg <th align="center"> max </tr> <tr> <th align="center"> <!--#stat data=minReqTime --> <th align="center"> <!--#stat data=meanReqTime --> <th align="center"> <!--#stat data=maxReqTime --> </tr> </table> <p>Thread counts: <table border> <tr> <th align="center"> free <th align="center"> idle <th align="center"> total </tr> <tr> <th align="center"> <!--#stat data=freeThreads --> <th align="center"> <!--#stat data=idleThreads --> <th align="center"> <!--#stat data=totalThreads --> </table> <p>Current load: <!--#stat data=serverLoad --> </body> </html> |