Resource class for yet another access count

I wrote a resource, called YAACResource, to generate an XBM image for
access counts (YAAC = Yet Another Access Count).  Configuring
YAACResource should be easy and straightforward (see the class
description).  Hope this is helpful and useful.

Critical comments are appreciated.

Cheers,
Albert

Albert X. Zhou ~{V\O~;*~}     | Opinions expressed are my own,   |
Nortel Technology             | not necessarily those of Nortel. |
P.O. Box 3511, Station C, Ottawa, Canada K1Y 4H7
Tel: (613) 763-9702; Fax: (613) 765-4855; E-Mail: azhou@nortel.ca


// $RCSfile: YAACResource.java,v $$Revision: 1.5 $$Date: 96/08/25 23:11:47 $

package w3c.jigsaw.contrib;

import java.io.*;
import java.net.*;
import java.util.*;
import w3c.jigsaw.auth.*;
import w3c.jigsaw.http.*;
import w3c.jigsaw.resources.*;

/**
 * This resource class generates an access count in the form of an XBM
 * image for a configurable YAAC (yet another access count) tag embedded
 * in an HTML page.
 * <p>The steps to configure and use this graphical access counter are
 * shown as follows:</p>
 * <ol>
 *   <li>Configure the YAAC resource in a server directory, say the
 *       root.  Attributes of the YAAC resource are rather self-
 *       explanatory.  The name of this resource could be any string,
 *       eg, Yaac.
 *   <li>Put an IMG tag in the HTML file where you want to show the
 *       access count with the SRC value set to the URI where the YAAC
 *       resource has been configured above.  For instance, if you add
 *       the YAAC resource with the name of Yaac to the root directory,
 *       the src should be set to "/Yaac".
 * </ol>
 * <p>The part of generating XBM image is based on a perl script by
 * <a href="http://www.lerc.nasa.gov/people/OmarSyed/">Omar Syed</a>.</p>
 * @version 0.13, 1996.08.26
 * @author Albert Zhou, azhou@nortel.ca
 */
public class YAACResource extends HTTPResource {
    protected static int ATTR_URLSTOCOUNT = -1; // URLs to count
    protected static int ATTR_IPSTOIGNORE = -1; // IPs to ignore
    protected static int ATTR_LEADINGZERO = -1; // show leading zeroes?
    protected static int ATTR_INVERSE     = -1; // inverse the counter image?

    /**
     * The yaac is the hashtable storing the access count values for
     * those URLs registered via the YAAC resource.
     */
    private static Hashtable yaac = new Hashtable();

    /**
     * The class variable digits stores the bitmaps for digits 0 to 9.
     */
    private static byte[] digits = {
        0x00,0x00,0x00,0x3c,0x66,0x66,0x66,0x66,
        0x66,0x66,0x66,0x66,0x3c,0x00,0x00,0x00,
        0x00,0x00,0x00,0x30,0x38,0x30,0x30,0x30,
        0x30,0x30,0x30,0x30,0x30,0x00,0x00,0x00,
        0x00,0x00,0x00,0x3c,0x66,0x60,0x60,0x30,
        0x18,0x0c,0x06,0x06,0x7e,0x00,0x00,0x00,
        0x00,0x00,0x00,0x3c,0x66,0x60,0x60,0x38,
        0x60,0x60,0x60,0x66,0x3c,0x00,0x00,0x00,
        0x00,0x00,0x00,0x30,0x30,0x38,0x38,0x34,
        0x34,0x32,0x7e,0x30,0x78,0x00,0x00,0x00,
        0x00,0x00,0x00,0x7e,0x06,0x06,0x06,0x3e,
        0x60,0x60,0x60,0x66,0x3c,0x00,0x00,0x00,
        0x00,0x00,0x00,0x38,0x0c,0x06,0x06,0x3e,
        0x66,0x66,0x66,0x66,0x3c,0x00,0x00,0x00,
        0x00,0x00,0x00,0x7e,0x66,0x60,0x60,0x30,
        0x30,0x18,0x18,0x0c,0x0c,0x00,0x00,0x00,
        0x00,0x00,0x00,0x3c,0x66,0x66,0x66,0x3c,
        0x66,0x66,0x66,0x66,0x3c,0x00,0x00,0x00,
        0x00,0x00,0x00,0x3c,0x66,0x66,0x66,0x66,
        0x7c,0x60,0x60,0x30,0x1c,0x00,0x00,0x00
    };

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

        try {
            cls = Class.forName("w3c.jigsaw.contrib.YAACResource");
        }
        catch (Exception e) {
            e.printStackTrace();
            System.exit(1);
        }

        // declare/register the attributes
        a = new StringArrayAttribute("URLstocount",
                                     null,
                                     Attribute.EDITABLE);
        ATTR_URLSTOCOUNT = AttributeRegistery.registerAttribute(cls, a);
        a = new IPTemplatesAttribute("IPstoignore",
                                     null,
                                     Attribute.EDITABLE);
        ATTR_IPSTOIGNORE = AttributeRegistery.registerAttribute(cls, a);
        a = new BooleanAttribute("Leadingzero",
                                 Boolean.TRUE,
                                 Attribute.EDITABLE);
        ATTR_LEADINGZERO = AttributeRegistery.registerAttribute(cls, a);
        a = new BooleanAttribute("Inverse",
                                 Boolean.TRUE,
                                 Attribute.EDITABLE);
        ATTR_INVERSE = AttributeRegistery.registerAttribute(cls, a);
    }

    /**
     * Get the list of resource URLs that are configured to count.
     * @return the URL list (of strings).
     */
    public String[] getURLs() {
        return (String[]) getValue(ATTR_URLSTOCOUNT, null);
    }

    /**
     * Get the list of IPs that should be ignored.
     * @return the IP list.
     */
    public short[][] getIPs() {
        return (short[][]) getValue(ATTR_IPSTOIGNORE, null);
    }

    /**
     * Get the leading zero boolean.
     * @return the leading zero boolean.
     */
    public boolean getLeadingZero() {
        return getBoolean(ATTR_LEADINGZERO, true);
    }

    /**
     * Get the inverse boolean.
     * @return the inverse boolean.
     */
    public boolean getInverse() {
        return getBoolean(ATTR_INVERSE, true);
    }

    /**
     * Get the access count for a desired URL.
     * @return the access count.
     */
    public synchronized int getCount(String url) {
        int count = 0;
        if (yaac.containsKey(url))
            count = ((Integer)yaac.get(url)).intValue();
        return count;
    }

    /**
     * Set the access count for a desired URL.
     */
    public synchronized void setCount(String url, int count) {
        yaac.put(url, new Integer(count));
        markModified(); // mark Yaac resource modified
    }

    /**
     * Is the requested URL configured for YAAC count?
     * @param url the requested URL
     * @return true if the URL is YAAC configured; false otherwise.
     */
    public boolean YAACConfigured(String url) {
        boolean found = false;
        String urls[] = getURLs();
        if (urls != null) {
            int i = 0;
            while (!found && i < urls.length)
                found = urls[i++].equals(url);
        }
        return found;
    }

    /**
     * Should the request host be ignored regarding the YAAC count?
     * @param ip the request host IP address in network bytes.
     * @return true if the host should be ignored; false otherwise.
     */
    public boolean hostToIgnore(byte[] ip) {
        boolean found = false;
        short ips[][] = getIPs();
        if (ips != null) {
            int i = 0;
            while (!found && i < ips.length) {
                found = true;
                for (int j = 0; j < ip.length; j++) {
                    if (ips[i][j] != 256) // match/ignore the wild card
                        found &= (byte)ips[i][j] == ip[j];
                }
                i++;
            }
        }
        return found;
    }

    /**
     * Display the access count for the referer URL if it's configured.
     * @param request the original request
     * @exception HTTPException If processing fails.
     */
    public Reply get(Request request) throws HTTPException
    {
        String referer = request.getField("referer");
        if (YAACConfigured(referer)) {
            int count = getCount(referer);

            Client client = request.getClient();
            InetAddress ipaddr = client.getInetAddress();
            if (!hostToIgnore(ipaddr.getAddress()))
                setCount(referer, ++count);

            Reply reply = request.makeReply(HTTP.OK);
            reply.setContentType("image/xbm");
            String image = createYAACImage(count, getLeadingZero(), getInverse());
            reply.setContent(image);

            return reply;
        }
        else {
            Reply error = request.makeReply(HTTP.NOT_ALLOWED);
            error.setContentType("image/xbm");
            throw new HTTPException(error);
        }
    }

    /**
     * Generate an XBM bitmap image for a YAAC access count.
     * @param count the access count
     * @param leadingzero the leading zeros desirable?
     * @param inverse the inverse display desirable?
     * @return the String containing the xbm image
     */
    protected String createYAACImage(int count, boolean leadingzero, boolean inverse)
    {
        StringBuffer sb = new StringBuffer(Integer.toString(count));
        int len = sb.length() > 7 ? sb.length() : 7;

        if (leadingzero) {
            int fill = len - sb.length();
            for (int i = 0; i < fill; i++)
                sb = sb.insert(0, '0');
        }

        StringBuffer image = new StringBuffer();
        image.append("#define count_width " + len*8 + "\n");
        image.append("#define count_height 16\n");
        image.append("static char count_bits[] = {\n");

        int digit;
        for (int y = 0; y < 16; y++) {
            for (int x = 0; x < len; x++) {
                digit = digits[((sb.charAt(x) - '0') * 16) + y];
                if (inverse) {
                    image.append(toHexString((((digit >> 4) ^ 0xf) & 0xf), ((digit ^ 0xf) & 0xf)));
                }
                else {
                    image.append(toHexString(((digit >> 4) & 0xf), (digit & 0xf)));
                }
                if (x < len-1)
                    image.append(',');
            }
            image.append((y == 15) ? "};" : ",");
            image.append('\n');
        }

        return image.toString();
    }

    /**
     * Convert an integer with high and low bytes to its equivalent Hex
     * representation starting with the hex indicator '0x'.
     * @param hi the high byte
     * @param low the low byte
     * @return the String containing the Hex string
     */
    private String toHexString(int hi, int low) {
        return "0x" + Integer.toString(hi << 4 | low, 16);
    }

    /**
     * Convert an integer to its equivalent Hex representation starting
     * with the hex indicator '0x'.
     * @param i the integer
     * @return the String containing the Hex string
     */
    private String toHexString(int i) {
        return "0x" + Integer.toHexString(i);
    }

    /**
     * Pickle a YAAC resource along with its associated counts.
     * @param out the data output stream to pickle to.
     */
    public synchronized void pickle(DataOutputStream out) throws IOException
    {
        super.pickle(out);
        // pickle the yaac counts
        if (yaac.isEmpty()) {
            out.writeInt(0);
        }
        else {
            out.writeInt(yaac.size());
            Enumeration urls = yaac.keys();
            String url;
            int count;
            while (urls.hasMoreElements()) {
                url = (String) urls.nextElement();
                count = getCount(url);
                out.writeUTF(url);
                out.writeInt(count);
            }
        }
    }

    /**
     * Unpickle a YAAC resource.
     * @param in the input stream to unpickle from.
     * @param defs the default attributes for the unpickled resource.
     */
    public AttributeHolder unpickleInstance(DataInputStream in, Hashtable defs) throws IOException
    {
        super.unpickleInstance(in, defs);
        // unpickle the yaac counts
        int size = in.readInt();
        if (size > 0) {
            yaac = new Hashtable();
            String url;
            int count;
            for (int i = 0; i < size; i++) {
                url = in.readUTF();
                count = in.readInt();
                setCount(url, count);
            }
        }
        return this;
    }

}

Received on Thursday, 29 August 1996 22:23:13 UTC