Friday, July 16, 2004

HTML and Color Popup Windows

The guys at Visibone have created some neat popup windows. From their site: The HTML Popup is a tall skinny on-screen quick reference showing all HTML tags and attributes in use or universal. It includes color-coded indications of support by Netscape®, Microsoft® Internet Explorer and W3C®, plus style sheet alternatives, and bug and clash icons with details online. The Color Popups show all 216 web-safe colors in a wheel.

Thursday, July 15, 2004

Java - Writing a Custom Web Server to Interpolate Velocity Templates.

This is the first entry of several that will chronicle my writing a self-contained web application designed to attach keywords to photo (or images). I did search with Google, but I saw no open-source applications that let you search through an image database. I'll be using my own embedded http server, Lucene, Prevayler, and Velocity.

Quite frankly, I don't like to deal with the configuration and deployment issues associated with Tomcat and JBoss. So I've modified the SingleFileHTTPServer written by Elliot Harrold to serve interpolated Velocity templates.

In order to get the MugTrapServer class to work, you'll need a config\mugtrap.properties file:

# The Velocity Properties
file.resource.loader.path=./src/velocity
file.resource.loader.cache=false
file.resource.loader.modificationCheckInterval=120
runtime.log.logsystem=org.apache.velocity.runtime.log.SimpleLog4JLogSystem
runtime.log.logsystem.log4j.category=org.wwre.velocity
input.encoding=UTF-8
output.encoding=UTF-8

You'll also need a starting Velocity template called src\velocity\index.vm:

<html>
  <head/>
  <body>
  	<br>
  	Statistics ===========================================<br>
	Request: ${request}<br>
    Pages Served: ${pagesServed}<br>
	HTTP Server Uptime: ${upTime}  ms.<br>
	Created At: ${creationDate}<br>
    = Documentation ===========================================<br>
    <a href="/stop">/stop</a> - ends the splitter process.<br>
  </body>
</html>

And finally, the source code:

package com.codebits.mugtrap;

import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InterruptedIOException;
import java.io.OutputStream;
import java.io.StringWriter;
import java.io.UnsupportedEncodingException;
import java.net.ServerSocket;
import java.net.Socket;
import java.text.DecimalFormat;
import java.util.Date;
import java.util.Properties;
import java.util.StringTokenizer;

import org.apache.velocity.VelocityContext;
import org.apache.velocity.app.Velocity;

public class MugTrapServer extends Thread {

    private static final String PROP_LOG4J_PROPERTY_FILE = "com.codebits.mugtrap.log4j.property.file";

    private static final String PROPERTY_FILE = "property.file";

    public static void main(String[] args) throws Exception {
        System.setProperty("property.file", "config/mugtrap.properties");
        
        MugTrapServer mts = new MugTrapServer();
        mts.initialize();
        
        Thread t = new VelocityServer();
        t.start();
    }

    private byte[] header;

    private int pagesServed = 1;

    private VelocityContext velocityContext = new VelocityContext();

    public MugTrapServer() throws UnsupportedEncodingException {
        StopWatch.add("httpServer");
        String header = "HTTP/1.0 200 OK\r\n" + "Server: WWRE HTTP Server 1.0\r\n" + "Content-type: text/HTML\r\n\r\n";
        this.header = header.getBytes("ASCII");
    }
    
    public void addObjectToContext(final String key, final Object object) {
        this.velocityContext.put(key, object);
    }
    
    public void initialize() {
        final String mugTrapPropertyFile = System.getProperty(PROPERTY_FILE);

        // read from the properties file.
        final Properties properties = new Properties();
        try {
            properties.load(new FileInputStream(mugTrapPropertyFile));
        } catch (IOException e) {
            e.printStackTrace();
            throw new RuntimeException(e.getMessage());
        }

        try {
            Velocity.init(properties);
        } catch (Exception e) {
            throw new RuntimeException(e.getMessage());
        }
    }

    public void interpolateTemplate(final StringWriter sw, final String templateName) {
        try {
            Velocity.mergeTemplate(templateName, "UTF-8", this.velocityContext, sw);
        } catch (Exception e) {
            throw new RuntimeException(e.getMessage());
        }
    }

    public void run() {
        boolean endProcess = false;
        
        try {
            ServerSocket server = new ServerSocket(9876);
            server.setSoTimeout(1000);

            while (!endProcess) {
                Socket connection = null;
                int bufferSize = 1000;
                StringBuffer request = new StringBuffer(bufferSize);

                try {
                    connection = server.accept();
                    OutputStream out = new BufferedOutputStream(connection.getOutputStream());
                    InputStream in = new BufferedInputStream(connection.getInputStream());
                    int bufferIndex = 0;
                    while (bufferIndex++ < bufferSize) {
                        int c = in.read();
                        if (c == '\r' || c == '\n' || c == -1)
                            break;
                        request.append((char) c);
                        // If this is HTTP/1.0 or later send a MIME header

                    }
                    StringBuffer buffer = new StringBuffer();

                    if (request.toString().indexOf("HTTP/") != -1) {
                        out.write(this.header);
                    }

                    DecimalFormat myFormat = (DecimalFormat) DecimalFormat.getInstance();
                    myFormat.setDecimalSeparatorAlwaysShown(true);

                    // Sample request: GET /index.vm HTTP/1.1
                    StringTokenizer st = new StringTokenizer(request.toString(), " ");
                    if (st.countTokens() != 3) {
                        throw new RuntimeException("Expected 3 tokens but found " + st.countTokens() + " in " + request.toString());
                    }

                    String httpCommand = st.nextToken();
                    String template = st.nextToken().substring(1); // ignore leading slash.
                    // I don't care about the third token.

                    if (template.toUpperCase().equals("STOP")) {
                        buffer.append("MugTrap server stopped.");
                        endProcess = true;
                    } else {
                        if (template.length() == 0 || template.endsWith(".vm")) {
                            template = "index.vm";
                        }
                        addObjectToContext("request", request.toString());
                        addObjectToContext("pagesServed", myFormat.format(pagesServed));
                        addObjectToContext("upTime", StopWatch.elapsedTime("httpServer"));
                        addObjectToContext("creationDate", new Date());

                        StringWriter sw = new StringWriter();
                        interpolateTemplate(sw, template);
                        buffer.append(sw.getBuffer());
                        sw.close();
                    }

                    out.write(buffer.toString().getBytes());
                    out.flush();
                    out.close();
                    pagesServed++;

                } catch (InterruptedIOException e) {
                    // ignore this error. when the access() times out, then
                    // a new accept() is started. The timeout lets calling
                    // processes easily stop the server.
                } catch (IOException e) {
                    throw new RuntimeException(e.getMessage());
                } finally {
                    if (connection != null) {
                        connection.close();
                    }
                }
            } // end while(!endProcess)
        } // end try
        catch (IOException e) {
            throw new RuntimeException(e.getMessage());
        }
    } // end run    
}

Thursday, July 01, 2004

Wow! Blogger has really improved its User Interface!

The 'Edit Posts' feature is quite nice. If you don't blog now, take a look.

Java; Using Flag Classes to Replace boolean values.

Let me supply some background information before talking about the Flag classes. Part of my project involves using Java classes to access XML. I don't want to use the currently available unmarshalling tools because I only need part of the XML data inside my Java application. Therefore, I use XPATHs to select the data as needed. In order to ensure that I can use the same class to selectively unmarshall from XML and provide the ability to run test cases, I use the following technique:

  private String senderName = null;

  public String getSenderName() {
    if (this.senderName == null) {
      this.senderName = XmlHelper.getTextAtXpath(getCurrentElement(), "./senderName");
    }
    return this.senderName;
  }

  public void setSenderName(final String _senderName) {
    this.senderName = _senderName;
  }

This technique works fine for strings because they are nullable. However, boolean values are harder to handle. So I create a Flag class:

public class MsgIsParent{

    private boolean value;

    public static final MsgIsParent TRUE = new MsgIsParent(true);
    public static final MsgIsParent FALSE = new MsgIsParent(false);

    private MsgIsParent(final boolean _value) {
        this.value = _value;
    }

    public static MsgIsParent factory(final boolean _value) {
        if (_value) {
            return TRUE;
        } else {
            return FALSE;
        }
    }
}

With this small helper available, I can follow the same technique that I used for Strings:

    private MsgIsParent msgIsParent = null;
    
    public MsgIsParent getMsgIsParent() {
        if (this.msgIsParent == null) {
            this.msgIsParent = MsgIsParent.factory(getRootElement().getNodeName().equals(getMessageType()));
        }
        return this.msgIsParent;
    }
    
    public void setMsgIsParent(final MsgIsParent _msgIsParent) {
        this.msgIsParent = _msgIsParent;
    }