A rough around the edge auto-saver

If everything is ok, you should be able to do the following:

a) Install the attached file in
       Jigsaw/src/classes/w3c/jigsaw/contrib/CheckpointResource.java
b) Compile that class
c) Install it into your URL space (typically in /Admin)

This resource will checkpoint your configuration every N seconds. Note
that this is roughly tested code, and not all features are implemented
(prop and log flush).

Problems:
- Unless you access it, it will not start (so you really have to GET
/Admin/checkpoint for it to start)
- If the resource gets unloaded for some reason, it will not start up
again.

These are two serious problems, my intent is:
a) For the first problem, maintain a list of resources that are to be
   loaded at startup time, ie, have a httpd property giving the name
   of a file consisting of a list of resource URL. After init, Jigsaw
   will look them up (which as a side effect will load them into
   memory) - This is I guess, what Alex suggested this morning.
b) The second problem is more complicated, it requires some API
   changes to be able, for a resource to reject a notifyUnload. Unless
   I find something better, I will probably make notifyUnload return a
   boolean, if false, the resource will *not* be unloaded. Alternative
   is to add a new API in w3c.jigsaw.resources.Resource, eg:
      public boolean isUnloadable()
   which would return true by default, before deciding to unload a
   resource, a resource store would invoke that method and behalf
   according to the result. Alternative allows for backward
   compatibility, but I guess no one have played with notifyUnload
   yet (?) (drawback of alternative is that notifyUnload is no longer
   atomic, which might be a problem...)

Criticisms, advices and opinions on these problems most welcome.

Anselm.

// CheckpointResource.java
// $Id$
// (c) COPYRIGHT MIT and INRIA, 1996.
// please first read the full copyright statement in file COPYRIGHT.HTML

package w3c.jigsaw.contrib;

import w3c.www.http.*;
import w3c.jigsaw.http.*;
import w3c.jigsaw.resources.*;
import w3c.jigsaw.html.*;
import java.util.*;

/**
 * A resource that will checkpoint the configuration at regular intervals.
 * This resource will make sure that current configuration is backed up to 
 * disk at regular (configurable) intervals.
 * <p>The webmaster can customize what part of the configuration is to be 
 * backed up through boolean attributes.
 */

public class CheckpointResource extends FilteredResource implements Runnable {
    /**
     * Attribute index - Backup interval, in seconds.
     */
    protected static int ATTR_INTERVAL = -1;
    /**
     * Attribute index - The priority of the flusher thread.
     */
    protected static int ATTR_PRIORITY = -1;
    /**
     * Attribute index - Should we flush the logs too ?
     */
    protected static int ATTR_FLUSHLOG = -1;
    /**
     * Attrbute index - Should we save the properties too ?
     */
    protected static int ATTR_FLUSHPROPS = -1;
    /**
     * Attribute index - Should we save the configuration ?
     */
    protected static int ATTR_FLUSHCONFIG = -1;

    static {
	Class     c = null;
	Attribute a = null;

	try {
	    c = Class.forName("w3c.jigsaw.contrib.CheckpointResource");
	} catch (Exception ex) {
	    ex.printStackTrace();
	    System.exit(1);
	}
	// Register the interval attribute:
	a = new IntegerAttribute("interval"
				 , new Integer(60)
				 , Attribute.EDITABLE);
	ATTR_INTERVAL = AttributeRegistry.registerAttribute(c, a);
	// Register the flusher thread priority
	a = new IntegerAttribute("thread-priority"
				 , new Integer(2)
				 , Attribute.EDITABLE);
	ATTR_PRIORITY = AttributeRegistry.registerAttribute(c, a);
	// Register the flushlog boolean property:
	a = new BooleanAttribute("flush-logs"
				 , Boolean.FALSE
				 , Attribute.EDITABLE);
	ATTR_FLUSHLOG = AttributeRegistry.registerAttribute(c, a);
	// Register the flush properties property:
	a = new BooleanAttribute("flush-properties"
				 , Boolean.FALSE
				 , Attribute.EDITABLE);
	ATTR_FLUSHPROPS = AttributeRegistry.registerAttribute(c, a);
	// Register the flush configuration property:
	a = new BooleanAttribute("flush-configuration"
				 , Boolean.TRUE
				 , Attribute.EDITABLE);
	ATTR_FLUSHCONFIG = AttributeRegistry.registerAttribute(c, a);
    }
    /**
     * Our thread, if one is currently attached.
     */
    protected Thread thread = null;
    /**
     * Last date at which we checkpointed the configuration
     */
    protected Date checkpoint = null;
    /**
     * Is our attached thread still alive ?
     */
    protected boolean alive = false;

    /**
     * Start the thread for this object, only if needed.
     */

    protected synchronized void activate() {
	if (getFlushLog() || getFlushProperties() || getFlushConfiguration()) {
	    if (getInterval() > 0) {
		alive = true;
		if (thread == null) {
		    thread = new Thread(this);
		    thread.setName("checkpointer");
		    thread.setPriority(getPriority());
		    thread.start();
		    return;
		} else {
		    return;
		}
	    }
	} 
	// The thread should not be running any more, stop and kill it:
	if ( thread != null ) {
	    alive = false;
	    thread.stop();
	    thread = null;
	}
	return;
    }

    /**
     * Force our attached thread to stop.
     */

    protected synchronized void stop() {
	alive  = false;
	thread = null;
	notify();
    }

    /**
     * Get the sync interval.
     * @return An integer number of seconds, or <strong>-1</strong> if 
     * undefined.
     */

    public int getInterval() {
	return getInt(ATTR_INTERVAL, -1);
    }

    /**
     * Get the priority for our attached thread.
     * @return An integer priority for the thread, which defaults to
     * <strong>2</strong> if undefined.
     */

    public int getPriority() {
	return getInt(ATTR_PRIORITY, 2);
    }

    /**
     * Get the flush log flag.
     * @return A boolean, <strong>true</strong> if the log is to be flushed at 
     * each refresh interval, <strong>false</strong> otherwise.
     */

    public boolean getFlushLog() {
	return getBoolean(ATTR_FLUSHLOG, false);
    }

    /**
     * Get the flush properties flag.
     * @return A boolean, <strong>true</strong> if the properties are to be
     * flushed, <strong>false</strong> otherwise.
     */

    public boolean getFlushProperties() {
	return getBoolean(ATTR_FLUSHPROPS, false);
    }

    /**
     * Get the flush configuration flag.
     * @return A boolean, <strong>true</strong> oif the configuration is to be
     * flushed at each interval, <strong>false</strong> otherwise.
     */

    public boolean getFlushConfiguration() {
	return getBoolean(ATTR_FLUSHCONFIG, true);
    }

    /**
     * This resource is being unloaded.
     * Unloading that object will also stop the thread. However, there is
     * a bug here, since if the resource gets unloaded for some reason, it
     * will not be able to wakeup itself at next checkpoint time.
     */

    public void notifyUnload() {
	stop();
	super.notifyUnload();
    }
     
    /**
     * We are attached a thread, now it's time to performt the job.
     */

    public void run() {
	httpd   server   = getServer();
	int     interval = -1;
	try {
	    while ( alive ) {
		// Are we still alive ?
		interval = getInterval();
		alive    = ((getFlushLog() 
			     || getFlushProperties() 
			     || getFlushConfiguration())
			    && (interval > 0 ));
		if ( ! alive )
		    break;
		// Wait for something to do:
		synchronized(this) {
		    try {
			wait(interval*1000);
		    } catch (InterruptedException ex) {
		    }
		} 
		// Do what is to be done:
		if (alive && getFlushConfiguration() )
		    server.checkpoint();
		checkpoint = new Date();
	    }
	} catch (Exception ex) {
	    String msg = (getClass().getName() + "@" + getURL()
			  +": exception while running \""
			  + ex.getMessage() + "\". Stopped.");
	    server.errlog(msg);
	} finally {
	    thread = null;
	}
    }

    /**
     * Get the content of that resources.
     * Will display some usefull commands to start/stop the attached thread
     * @param request The request to handle.
     * @exception HTTPException If request processing failed.
     */

    public Reply get(Request request) 
	throws HTTPException 
    {
	String query = request.getQueryString();
	if ( query != null ) {
	    if ( query.equals("start") ) {
		// Start the thread if needed
		activate();
	    } else if (query.equals("stop") ) {
		// Stop the thread
		stop();
	    }
	}
	// Emit output:
	HtmlGenerator g = new HtmlGenerator("CheckpointResource");
	g.append("<h1>CheckpointResource status</h1>");
	g.append("<p>Checkpoint is currently "
		 , ((thread == null) ? " stopped " : "running")
		 , ".");
	g.append("<hr>You can:<p><dl>");
	g.append("<dt><a href=\""
		 , getURL()
		 , "?start\">start</a><dd>Start the checkpointer.");
	g.append("<dt><a href=\""
		 , getURL()
		 , "?stop\">stop</a><dd>Stop the checkpointer.");
	g.append("</dl><hr>Last checkpoint at <strong>"
		 , ((checkpoint == null) 
		    ? "no checkpoint run yet" 
		    : checkpoint.toString())
		 , "</strong>.");
	Reply reply = createDefaultReply(request, HTTP.OK);
	reply.setStream(g);
	return reply;
    }

    /**
     * Activate the checkpointer at initialization time.
     * @poaram values Default attribute values.
     */

    public void initialize(Object values[]) {
	super.initialize(values);
	activate();
    }
    
}

Received on Thursday, 14 November 1996 07:37:42 UTC