Monday, October 6, 2008

A complete Click application configuration example

Click applications are configured through the ConfigService interface.

The default ConfigService implementation is XmlConfigService which is configured through the click.xml file.

click.xml is defined by a DTD which some folk find easy to understand and follow. I however, am not one of them.

I enjoy learning from examples (not to mention it allows for easy copying and pasting) so I've put together an example of all the configuration options specified by the DTD:


<!-- A Click Application (click.xml) Example. -->
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<click-app charset="UTF-8" locale="en">

<!-- A automatically mapped Page package. Note automapping and autobinding is true by default -->
<pages package="com.mycorp.banking.page" automapping="true" autobinding="true">
<!-- A custom mapped Page -->
<page path="index.htm" classname="com.mycorp.page.Home"/>

<!-- Another mapped Page with custom headers -->
<page path="login.htm" classname="com.mycorp.page.Login">
<!-- Specify headers to cache the page for 1 hour, after which it should be reloaded -->
<header name="Pragma" value="no-cache"/>
<header name="Expires" value="1" type="Date"/>
</page>

</pages>

<!-- Another automatically mapped Page package -->
<pages package="com.mycorp.common.page"/>

<!-- Setup global headers. The headers shown below is the default used by Click -->
<headers>
<header name="Pragma" value="no-cache"/>
<header name="Cache-Control"
value="no-store, no-cache, must-revalidate, post-check=0, pre-check=0"/>
<header name="Expires" value="1" type="Date"/>
</headers>

<!-- Setup alternative Format. Default Format is net.sf.click.util.Format -->
<format classname="com.mycorp.util.Format"/>

<!-- Mode values include: [production], [profile], [development], [debug], [trace] -->
<mode value="production"/>

<!-- Set Click internal Logger to Log4J instead of the default ConsoleLogService -->
<log-service classname="net.sf.click.extras.service.Log4JLogService"/>

<!-- Set the template engine to use Freemarker instead of Velocity -->
<template-service classname="net.sf.click.extras.service.FreemarkerTemplateService"/>

<!-- Set the net.sf.click.service.CommonsFileUploadService properties: sizeMax and fileSizeMax. -->
<file-upload-service>
<!-- Set the total request maximum size to 10mb (10 x 1024 x 1024 = 10485760). The default request upload size is unlimited. -->
<property name="sizeMax" value="10485760"/>

<!-- Set the maximum individual file size to 2mb (2 x 1024 x 1024 = 2097152). The default file upload size is unlimited. -->
<property name="fileSizeMax" value="2097152"/>
</file-upload-service>
<!-- The commented section below shows how to use the classname attribute to specify -->
<!-- a custom net.sf.click.service.FileUploadService implementation. -->
<!--
<file-upload-service classname="com.mycorp.util.CustomFileUploadService">
<property name="sizeMax" value="10485760"/>
<property name="fileSizeMax" value="2097152"/>
</file-upload-service>
-->

<!-- List controls which will deploy their resources on application startup -->
<controls>
<control classname="net.sf.click.examples.control.FilterPanel"/>

<!-- A control-set which refers to a third-party xml file specifying the list of controls to deploy -->
<control-set name="mycorp-third-party-controls.xml"/>

<!-- Example mycorp-third-party-controls.xml file -->
<!--
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<click-app>
<controls>
<control classname="com.mycorp.control.MyCorpTable"/>
<control classname="com.mycorp.control.MyCorpForm"/>
</controls>
</click-app>
-->

</controls>

</click-app>

Sunday, September 28, 2008

Make your Click pages load faster!

Yahoo published a list of best practices for improving web application performance.

Click Framework provides a PerformanceFilter which caters for some of these rules. However not all rules can be easily automated.

This entry will outline ways to apply rules which are not covered by the PerformanceFilter namely, #1 - Minimize HTTP Requests (by combining files) and #10 - Minify Javascript and CSS.

Rule #1 also mentions CSS Sprites, a method for combining multiple images into a single master image. CSS Sprites is not covered here.

It is worth pointing out that its not necessary to blindly optimize every page in an application. Instead concentrate on popular pages, for example a web site's Home Page would be a good candidate.

There are a couple of tools that are useful in applying Rule #1 and #10:

  • YUICompressor - minifies and compresses JavaScript and CSS files so less bytes have to be transferred across the wire.
  • Ant Task for YUICompressor - an Ant task that uses YUICompressor to compress JavaScript and CSS files.
  • JSMin - similar to YUICompressor but only minifies (remove whitespace and newlines) JavaScript files and does no compression at all. An advantage of JSMin over YUICompressor is that its faster and can be used at runtime to minify JavaScript, while YUICompressor is most often used at build time.
Below are some articles outlining how to use YUICompressor and Ant to concatenate and compress JavaScript and CSS files:
  • Article explaining how to use Ant and YUICompressor for compression.
  • Article outlining how to use a special YUICompressor Ant Task for compression.
Using one of the approaches above one can concatenate and compress all JavaScript and CSS for a Page into two separate files, for example home-page.css and home-page.js. Note that the two files must include all the JavaScript and CSS that is generated by the Page and its Controls. Then one can instruct Click to only include the two compressed files, home-page.css and home-page.js.

Click makes use of the utility class PageImports to include the CSS and JavaScript. PageImports exposes the method setInitialized(boolean), which controls when PageImports are fully initialized. Once PageImports have been initialized, no other CSS and JavaScript will be included.

Knowing this one can override Page.getPageImports(), and import the necessary JavaScript and CSS files and then set PageImports to initialized, forcing PageImports to skip other CSS and JavaScript files.

Here is an example of how to apply this in Click:
public class HomePage extends Page {

private Form form = new Form("form");

public void onInit() {
form.add(new DateField("date");
addControl(form);
}

public void getPageImports () {
PageImports pageImports = super.getPageImports();
String contextPath = getContext().getRequest().getContextPath();

String cssInclude = contextPath + "/assets/css/home-page.css";
pageImports.addImport("<link type=\"text/javascript\" href=\""
+ cssInclude + "\"/>");

String jsInclude = contextPath + "/assets/js/home-page.js";
pageImports.addImport("<script type=\"text/javascript\" src=\""
+ jsInclude + "\"></script>");

// Set pageImports to initialized so that no other CSS and JavaScript files will be included.
pageImports.setInitialized(true);
}
}
Using the following Velocity border-template.htm:
<html>
<head>
<title>Click Examples</title>
${cssImports}

</head>
<body>

...

${jsImports}
</body>
</html>

the rendered HTML will include one CSS and one JavaScript import:
<html>
<head>
<title>Click Examples</title>
<link type="text/css" rel="stylesheet" href="/click-examples/assets/css/home-page.css"
title="Style"/>
</head>

<body>

...

<script type="text/javascript" src="/click-examples/assets/js/home-page.js"></script>
</body>
</html>
A live demo is available here

Monday, April 7, 2008

Web Framework Smackdown Questions: Click Framework

For his Web Framework Smackdown event at TheServerSide conference, Matt Raible posted a list of questions that people wanted to know about each framework.

Tapestry already responded and I think there is merit in answering these questions for developers to quickly gauge if a particular framework fits their needs.

  • Q1: What is the overall performance of your framework as it compares to others?

    There is a FAQ topic on performance which outlines the amount of work done by Click per request. In short it creates a small amount of short lived objects and uses very little reflection. For rendering Click uses a non-blocking string buffer and estimates the output size to avoid unnecessary memory allocation and arraycopy operations.

    I also ran a benchmark to test how fast Click was in practice. The benchmark used was adopted from a similar effort started by Apache Wicket and can be downloaded here.

    It tests the performance of rendering a Table consisting of 50 rows by 5 columns. There is no database involved. The data is randomly generated at the start of the application and served statically.

    On a dual core Dell Laptop, 32-bit, T2600 @ 2.0 GHz, Tomcat 6.0.13, -Xmx512m, Windows XP, JDK6 the results for Click was:

    ~400 requests per second.

    On a core 2 duo desktop PC, 64-bit, E8400 @ 3.0 GHz, Tomcat 6.0.13, -Xmx512m, Linux Ubunutu, JDK6 the results for Click was:

    ~1500 requests per second.

    On a proper server I doubt Click will ever be the bottleneck.

    It is worth mentioning that Click is a stateless component oriented framework, thus scalability should be as easy (or difficult) to achieve as with an action oriented framework.

  • Q2: How does your web framework position themselves in relation to Web Beans?

    From my understanding Web Beans is targeted at JSF and EJB3. There is no relation to Click.

  • Q3: How easy is it to create a re-useable component in your framework? Is it as easy as sub-classing an existing component?

    Components are written in Java so it is straightforward to write new ones. Either implement the Control interface or extend one of the existing controls.

    For example if you want to have a link that always references your home page, you can extend the PageLink (which enables linking to specific pages) and override the #getPageLink() method to return HomePage.class. Here is the code:
    public HomeLink extends PageLink {
    public HomeLink() {
    setName("Home");
    }

    // Overridden to always return home page.
    public Class getPageClass() {
    return HomePage.class;
    }
    }
  • Q4: What is the key differentiating characteristic of your framework that makes it better than the rest?

    In short: simplicity. However since all frameworks promote simplicity as one of their benefits let me elaborate a bit.

    Click's simplicity and ease of use are realized through a lack of complex abstractions.

    Have a look at the core and extras API to see what I mean.

    There are very few interfaces and abstract classes. Click's target audience is the application developer who has limited time to spent learning new frameworks. Click focuses on solving actual problems instead of adding new buzzwords to its feature list.

  • Q5: What do you think about the various scopes introduced by Seam, e.g. conversation vs request or session? If you support these additional scopes, do you also provide some sort of concurrency control?

    I'll answer these questions separately.

    • 5.1: Perhaps I am showing my ignorance here but it seems conversation scope tries to solve a problem invented by JSF?

      I would argue that one should not try and be too smart about this. Stick to what has always worked well: after each request save the state to the database and enjoy your Saturday evening at the theater, as you won't have any problems to worry about.

    • 5.2: Click does not support conversation scope as outlined by Seam. However the traditional way of having a conversation scope is by passing variables inside hidden fields or as hyperlink parameters.

      To see this in action have a look at the table paging and sorting example.

      If you open a couple of tabs you will notice that the table sorting and paging does not interfere with one another. This way of handling conversation works really well and keeps your session free of state.

  • Q6: Why can't we, the Java Community, come together and adopt the best application framework and settle the web development subject?

    We already did it once before with Struts so I guess it is possible. But back then Struts was the only option and the choice was easy. I think it will be difficult to find a framework which satisfies everybody in every industry. Some folks like standardization, others like simplicity and yet others like sophisticated frameworks.

  • Q7: What are you doing to help with developer productivity?

    Click tries to help with productivity as follows:

    • From a design perspective Click is as simple as possible.

    • Good documentation explains the design clearly.

    • Components know how to draw themselves which alleviates the developer from maintaining redundant markup. Meaning if you define a Table or Form in your Page, you won't have to redefine that component in your template.

    • Use convention over configuration.

    • Great error reporting both in the logs and browser.

    • Lots of examples and best practices to learn from.

  • Q8: 2008 is a huge year for the mobile web. How do you help developers build great mobile web applications?

    Not really my field of expertise but Click does not in any way hinder the building of mobile applications.

  • Q9: If you couldn't use your framework, what would you use and why?

    I will use an action based framework, probably Stripes. Stripes is well documented and seems easy to learn and understand. It uses convention over configuration and focuses on the common problems instead of the exceptions.

  • Q10: How do you enable rich Ajax applications?

    Click supports clean URL's thus making callbacks from your favorite Ajax library is straightforward. You can also create custom Ajax components that deploy their own javascript libraries at runtime.

  • Q11: Can a developer make a change to source, and hit RELOAD in the browser to see the change? If not, why not?

    Click uses Velocity or JSP for templating. Both will reload a template if it changes.

    However components and pages does not support auto reloading when they are recompiled. There are some code lying around that enables this feature by using the parent last ClassLoader trick, but it has not been integrated into trunk yet.

    One snag with automatic reloading is that it ends up as an advanced feature because web frameworks cannot make it transparent. For example we can reload Click pages and components, but not Hibernate entities or Spring configuration changes. In the end the developer has to understand Java ClassLoading issues and why certain changes are not refreshed.

    I am hoping that the JVM can someday solve reloading natively. There is in fact a RFE filed for this: RFE 4910812

    On the commercial side JavaRebel claims to have solved the issue at the JVM level already. Sounds good to me!

  • Q12: What do you think about the whole Flex revolution, and do you think you are competitors to this technology?

    Flex + Flash are great technologies for building rich applications. On the other hand so is Swing, Eclipse and Netbeans RCP. All depends on the clients need really.

    Since Flex is client side and Click server side, I don't see much overlap. One can certainly use them together. ;-)

  • Q13: How easy is it to create a module and plug it into a bigger application, complete with configuration, code, and view?

    This feature is not available yet but scheduled for the next release.

Wednesday, February 27, 2008

Track non-serializable objects in Click Framework

Click 1.4 provides support for stateful pages by adding the page as an attribute to the HttpSession.

Now I guess your saying, “But what if my page is not serializable?“.

Obviously if your page or any reference on the page is not serializable you will receive a NotSerializableException.

java.io.NotSerializableException: za.co.abc.presentation.control.Three
at java.io.ObjectOutputStream.writeObject0(ObjectOutputStream.java:1153)
at java.io.ObjectOutputStream.defaultWriteFields(ObjectOutputStream.java:1509)
at java.io.ObjectOutputStream.writeSerialData(ObjectOutputStream.java:1474)
at java.io.ObjectOutputStream.writeOrdinaryObject(ObjectOutputStream.java:1392)
The problem with NotSerializableException is that it does not always provide you with the information you need to track down the object. Sure it might tell you which object is problematic, but not how to track it.

JDK6 provides a nice solution for this. Start the JVM with the option -Dsun.io.serialization.extendedDebugInfo=true and the stackTrace will look like this:
java.io.NotSerializableException: za.co.abc.presentation.control.Three
- field (class "za.co.abc.presentation.control.Two", name: "three", type: "class za.co.abc.presentation.control.Three")
- object (class "za.co.abc.presentation.control.Two", za.co.abc.presentation.control.Two@fbbb20)
- field (class "za.co.abc.presentation.control.One", name: "two", type: "class za.co.abc.presentation.control.Two")
- object (class "za.co.abc.presentation.control.One", za.co.abc.presentation.control.One@17ee6c9)
- field (class "za.co.abc.presentation.control.Trail", name: "one", type: "class za.co.abc.presentation.control.One")
- root object (class "za.co.abc.presentation.control.Trail", {/click-tests/home.htm=home})
at java.io.ObjectOutputStream.writeObject0(ObjectOutputStream.java:1153)
at java.io.ObjectOutputStream.defaultWriteFields(ObjectOutputStream.java:1509)
at java.io.ObjectOutputStream.writeSerialData(ObjectOutputStream.java:1474)
at java.io.ObjectOutputStream.writeOrdinaryObject(ObjectOutputStream.java:1392)
Reading the strackTrace from the bottom up one can see that the root object Trail references an object of type One which references an object of type Two which references an object of type Three which is not-serializable.

All good and well for JDK6 users, but what if you're still on JDK 1.4?

Bob Lee has provided a similar solution for tracking serialization errors and this will work in JVM's prior to JDK6.

Let's look at how we can apply his solution to detect and trace serialization errors when storing objects as attributes in the HttpSession.

We do this by implementing HttpSessionAttributeListener and whenever an attribute is added or replaced we serialize the new value and log out any errors.

Here is the output from the listener:
[Click] [error] Attribute => {trail, class za.co.abc.presentation.control.Trail} is not serializable.
Path to bad object:
- za.co.abc.presentation.control.Trail@c38d79cb,
- za.co.abc.presentation.control.One@1a505be,
- za.co.abc.presentation.control.Two@9740de,
- za.co.abc.presentation.control.Three@f3d0fc
Very similar to the JDK6 output but now read the output from top to bottom. Trail references an object of type One which in turn references an object of type Two which references an object of type Three which is not-serializable.

To use this in your own Click project simply copy the class below into your project and add the following snippet to your web.xml:
<listener>
<listener-class>yourpackage.AttributeSerializationDebugger</listener-class>
</listener>
Java class:
package yourpackage;

import java.io.ByteArrayOutputStream;
import java.io.ObjectOutputStream;
import java.io.OutputStream;
import java.io.IOException;
import java.lang.reflect.Field;
import java.util.List;
import java.util.ArrayList;
import java.util.Iterator;
import javax.servlet.http.HttpSessionAttributeListener;
import javax.servlet.http.HttpSessionBindingEvent;
import net.sf.click.Control;
import net.sf.click.util.ClickLogger;
import net.sf.click.util.ClickUtils;
import net.sf.click.util.HtmlStringBuffer;

/**
* Adapted from the article: http://crazybob.org/2007/02/debugging-serialization.html
*
*

* <listener>
* <listener-class>yourpackage.AttributeSerializationDebugger</listener-class>
* </listener>
*

*
* You can get the same functionality from Sun JDK6 when specifying the JVM options: -Dsun.io.serialization.extendedDebugInfo=true
*/
public class AttributeSerializationDebugger implements HttpSessionAttributeListener {

public void attributeAdded(HttpSessionBindingEvent event) {
testSerialization(event.getName(), event.getValue());
}

public void attributeRemoved(HttpSessionBindingEvent event) {
}

public void attributeReplaced(HttpSessionBindingEvent event) {
testSerialization(event.getName(), event.getValue());
}

public void testSerialization(String name, Object value) {
DebuggingObjectOutputStream out = null;
try {
out = new DebuggingObjectOutputStream(new ByteArrayOutputStream());
out.writeObject(value);
} catch (Exception e) {
HtmlStringBuffer msg = new HtmlStringBuffer();
msg.append("Attribute => {");
msg.append(name);
msg.append(", ");
msg.append(value.getClass());
msg.append("} is not serializable.\n\tPath to bad object:");
msg.append(out.getStack());
msg.append("\n\n\tException message: ");
msg.append(e.getMessage());
ClickLogger.getInstance().error(msg);
} finally {
ClickUtils.close(out);
}
}

static class DebuggingObjectOutputStream extends ObjectOutputStream {

private static final Field DEPTH_FIELD;

static {
try {
DEPTH_FIELD = ObjectOutputStream.class.getDeclaredField("depth");
DEPTH_FIELD.setAccessible(true);
} catch (NoSuchFieldException e) {
throw new AssertionError(e);
}
}

final List stack = new ArrayList() {

public String toString() {
HtmlStringBuffer buf = new HtmlStringBuffer();
Iterator i = iterator();

boolean hasNext = i.hasNext();

while (hasNext) {
Object o = i.next();
buf.append("\n\t- ");
buf.append(o == this ? "(this Collection)" : String.valueOf(o));
hasNext = i.hasNext();
if (hasNext) {
buf.append(", ");
}
}

return buf.toString();
}
};

/**
* Indicates whether or not OOS has tried to
* write an IOException (presumably as the
* result of a serialization error) to the
* stream.
*/
boolean broken = false;

public DebuggingObjectOutputStream(
OutputStream out) throws IOException {
super(out);
enableReplaceObject(true);
}

/**
* Abuse {@code replaceObject()} as a hook to
* maintain our stack.
*/
protected Object replaceObject(Object o) {
// ObjectOutputStream writes serialization
// exceptions to the stream. Ignore
// everything after that so we don't lose
// the path to a non-serializable object. So
// long as the user doesn't write an
// IOException as the root object, we're OK.
int currentDepth = currentDepth();
if (o instanceof IOException && currentDepth == 0) {
broken = true;
}
if (!broken) {
truncate(currentDepth);
stack.add(toString(o));
}
return o;
}

protected String toString(Object o) {
HtmlStringBuffer buffer = new HtmlStringBuffer(30);
buffer.append(o.getClass().getName() + "@" + Integer.toHexString(o.hashCode()));
if (o instanceof Control) {
Control c = (Control) o;
buffer.append(" controlName : " + c.getName());
}
return buffer.toString();
}

private void truncate(int depth) {
while (stack.size() > depth) {
pop();
}
}

private Object pop() {
return stack.remove(stack.size() - 1);
}

/**
* Returns a 0-based depth within the object
* graph of the current object being
* serialized.
*/
private int currentDepth() {
try {
Integer oneBased = ((Integer) DEPTH_FIELD.get(this));
return oneBased.intValue() - 1;
} catch (IllegalAccessException e) {
throw new AssertionError(e);
}
}

/**
* Returns the path to the last object
* serialized. If an exception occurred, this
* should be the path to the non-serializable
* object.
*/
public List getStack() {
return stack;
}
}
}