Archive for the ‘Wicket’ Category

Scala exercise 6: Tackling the Wicket hierarchy mismatch problem

Sunday, November 7th, 2010

Introduction

This is the sixth exercise in my Scala exercises series. If you haven’t seen it before, you may want to start from the previous exercises. Below is exercise 6: Tackling the Wicket hierarchy mismatch problem.

Some people have alleged that a weakness in Wicket is that you have to keep the hierarchy of the component tree in sync with that of the HTML elements. For example, for the HTML code:

<html>
    <form wicket:id="f">
        <input type="text" wicket:id="num">
        <input type="submit" value="OK">
    </form>
    <span wicket:id="result">10</span>
</html>

You would construct a component tree (in Scala) like:

class MyPage extends WebPage {
    val f = new Form[MyPage]("f") {
      override def onSubmit() {
        ...
      }
    }
    add(f)  //add the form to the page
    val numField = new TextField[Int]("num")
    f.add(numField)  //add the text field to the form
    val r = new Label("result")
    add(r) //add the label to the page
}

The problem is that, if, say, you’d like to move the result span into the form, you must change the code accordingly:

class MyPage extends WebPage {
    ...
    val r = new Label("result")
    f.add(r) //you must add the label to the form, not to the page!
}

and a common problem is that we may forget to do so. While there is no easy way to solve this problem, you could make the Scala code reflect the HTML structure visually (and look more like a declarative UI):

class MyPage extends WebPage {
  //the "ctx" represents a surrounding container context for the
  //construction of child components
  this containing { ctx =>
    val f = ...
    ctx.add(f)  //add the form to the context (the page)
    f containing { ctx =>
      val numField = ...
      ctx.add(numField) //add the text field to the context (the form)
    }
    val r = new Label("result")
    ctx.add(r)  //add the label to the context (the page)
  }
}

Note that the Scala code reflects the structure of the HTML code so you can compare them visually. In addition, if you need to move the label into the form, you can simply cut and paste the construction code of the label into that context of the form:

class MyPage extends WebPage {
  //the "ctx" represents a surrounding container context for the
  this containing { ctx =>
    val f = ...
    ctx.add(f)
    f containing { ctx =>
      val numField = ...
      ctx.add(numField)
      //the following lines were simply cut and pasted into here
      val r = new Label("result")
      ctx.add(r)
    }
  }
}

Now, your task is to implement this solution by creating the necessary code. Try to do it now.

See the answer here.

Getting started with Scala, Spring, Hibernate & Wicket

Saturday, June 19th, 2010

Introduction

Below is a tutorial to document what I did to create a fully working Scala project utilizing the best or the most popular frameworks or tools in enterprise Java such as Spring, Hibernate/JPA, Wicket, Maven and Intellij IDEA. The purpose is to help other Java programmers get started quickly with a fully working enterprise Scala project.

Setting up the IDE

First, download the Intellij IDEA 9.0.x (community edition) as it is the best scala IDE right now. Then choose File | Settings | Plugins, choose the Available tab to install the Scala plugin.

Creating the Maven project

In IDEA, choose File | New Project and choose the Maven module to create a Maven project. Then modify pom.xml as shown below. This will add all the dependencies you need and set up the compilation of Scala classes in the build processes:

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <groupId>com.foo</groupId>
    <artifactId>myapp</artifactId>
    <packaging>war</packaging>
    <version>1.0-SNAPSHOT</version>
    <name>myapp Java EE 6 Webapp</name>
    <url>http://maven.apache.org</url>
    <properties>
        <spring.version>3.0.3.RELEASE</spring.version>
    </properties>
    <repositories>
        <repository>
            <id>java.net2</id>
            <name>Repository hosting the jee6 artifacts</name>
            <url>http://download.java.net/maven/2</url>
        </repository>
        <repository>
            <id>scala-tools.org</id>
            <name>Scala-tools Maven2 Repository</name>
            <url>http://scala-tools.org/repo-releases</url>
        </repository>
        <repository>
            <id>wpt-release</id>
            <url>http://wicketpagetest.sourceforge.net/m2-repo/releases</url>
        </repository>
        <repository>
            <id>wpt-snapshot</id>
            <url>http://wicketpagetest.sourceforge.net/m2-repo/snapshots</url>
        </repository>
    </repositories>
    <pluginRepositories>
        <pluginRepository>
            <id>scala-tools.org</id>
            <name>Scala-tools Maven2 Repository</name>
            <url>http://scala-tools.org/repo-releases</url>
        </pluginRepository>
    </pluginRepositories>
    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>org.springframework</groupId>
                <artifactId>spring-context</artifactId>
                <version>${spring.version}</version>
            </dependency>
            <dependency>
                <groupId>org.springframework</groupId>
                <artifactId>spring-core</artifactId>
                <version>${spring.version}</version>
            </dependency>
            <dependency>
                <groupId>org.springframework</groupId>
                <artifactId>spring-web</artifactId>
                <version>${spring.version}</version>
            </dependency>
            <dependency>
                <groupId>org.springframework</groupId>
                <artifactId>spring-orm</artifactId>
                <version>${spring.version}</version>
            </dependency>
        </dependencies>
    </dependencyManagement>
    <dependencies>
        <dependency>
            <groupId>com.h2database</groupId>
            <artifactId>h2</artifactId>
            <version>1.2.120</version>
        </dependency>
        <dependency>
            <groupId>org.hibernate</groupId>
            <artifactId>hibernate-entitymanager</artifactId>
            <version>3.4.0.GA</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-orm</artifactId>
        </dependency>
        <dependency>
            <groupId>com.ttdev</groupId>
            <artifactId>wpt-core</artifactId>
            <version>1.5.2-SNAPSHOT</version>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>com.ttdev</groupId>
            <artifactId>wpt-runtime-spring</artifactId>
            <version>1.5.2-SNAPSHOT</version>
        </dependency>
        <dependency>
            <groupId>javax</groupId>
            <artifactId>javaee-web-api</artifactId>
            <version>6.0</version>
            <scope>provided</scope>
        </dependency>
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>3.8.2</version>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.scala-lang</groupId>
            <artifactId>scala-library</artifactId>
            <version>2.8.0.RC3</version>
        </dependency>
        <dependency>
            <groupId>org.apache.wicket</groupId>
            <artifactId>wicket</artifactId>
            <version>1.4.9</version>
        </dependency>
        <dependency>
            <groupId>org.slf4j</groupId>
            <artifactId>slf4j-log4j12</artifactId>
            <version>1.4.2</version>
        </dependency>
    </dependencies>
    <build>
        <pluginManagement>
            <plugins>
                <plugin>
                    <groupId>org.apache.maven.plugins</groupId>
                    <artifactId>maven-compiler-plugin</artifactId>
                    <version>2.0.2</version>
                </plugin>
                <plugin>
                    <groupId>org.scala-tools</groupId>
                    <artifactId>maven-scala-plugin</artifactId>
                    <version>2.9.1</version>
                </plugin>
            </plugins>
        </pluginManagement>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <executions>
                    <execution>
                        <phase>compile</phase>
                        <goals>
                            <goal>compile</goal>
                        </goals>
                    </execution>
                </executions>
                <configuration>
                    <source>1.6</source>
                    <target>1.6</target>
                </configuration>
            </plugin>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-war-plugin</artifactId>
                <version>2.1-beta-1</version>
                <configuration>
                    <failOnMissingWebXml>false</failOnMissingWebXml>
                </configuration>
            </plugin>
            <plugin>
                <groupId>org.scala-tools</groupId>
                <artifactId>maven-scala-plugin</artifactId>
                <executions>
                    <execution>
                        <id>scala-compile-first</id>
                        <phase>process-resources</phase>
                        <goals>
                            <goal>add-source</goal>
                            <goal>compile</goal>
                        </goals>
                    </execution>
                    <execution>
                        <id>scala-test-compile</id>
                        <phase>process-test-resources</phase>
                        <goals>
                            <goal>testCompile</goal>
                        </goals>
                    </execution>
                </executions>
            </plugin>
        </plugins>
        <finalName>myapp</finalName>
    </build>
</project>

Wait for a while and IDEA will prompt you on whether to import the changes into the project. Say yes.

Setting up web.xml

Next, modify main/webapp/WEB-INF/web.xml as below. This sets up the Wicket filter, the Spring filter to open the JPA entity manager and the Spring listener to initialize Spring itself.

<?xml version="1.0" encoding="UTF-8"?>
<web-app version="3.0" xmlns="http://java.sun.com/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd">
    <filter>
        <filter-name>f2</filter-name>
        <filter-class>org.springframework.orm.jpa.support.OpenEntityManagerInViewFilter</filter-class>
    </filter>
    <filter>
        <filter-name>f1</filter-name>
        <filter-class>org.apache.wicket.protocol.http.WicketFilter</filter-class>
        <init-param>
            <param-name>applicationClassName</param-name>
            <param-value>com.foo.myapp.MyApp</param-value>
        </init-param>
    </filter>
    <filter-mapping>
        <filter-name>f1</filter-name>
        <url-pattern>/app/*</url-pattern>
    </filter-mapping>
    <filter-mapping>
        <filter-name>f2</filter-name>
        <url-pattern>/*</url-pattern>
    </filter-mapping>
    <context-param>
        <param-name>contextConfigLocation</param-name>
        <param-value>classpath:/beans.xml</param-value>
    </context-param>
    <listener>
        <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
    </listener>
</web-app>

Setting up the Spring beans for database access and transaction

To define those Spring beans, create main/resources/beans.xml with the content below. This defines the entity manager factory, the transaction manager and etc.

<?xml version="1.0" encoding="UTF-8" ?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
       xmlns:tx="http://www.springframework.org/schema/tx"
       xsi:schemaLocation="http://www.springframework.org/schema/beans

http://www.springframework.org/schema/beans/spring-beans-3.0.xsd

http://www.springframework.org/schema/context

http://www.springframework.org/schema/context/spring-context-3.0.xsd

http://www.springframework.org/schema/tx

http://www.springframework.org/schema/tx/spring-tx-3.0.xsd">

    <context:component-scan base-package="com.foo.myapp"/>
    <bean id="emf" class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean">
        <property name="persistenceXmlLocation" value="classpath:/META-INF/my-persistence.xml"/>
    </bean>
    <bean id="transactionManager" class="org.springframework.orm.jpa.JpaTransactionManager">
        <property name="entityManagerFactory" ref="emf"/>
    </bean>
    <tx:annotation-driven />
</beans>

The entity manager factory will read the database configuration from the my-persistence.xml file. So, create it in main/resources/META-INF with the content below. Here, you’ll access an H2 database named myapp in your home directory.

<persistence xmlns="http://java.sun.com/xml/ns/persistence"
             xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
             xsi:schemaLocation="http://java.sun.com/xml/ns/persistence http://java.sun.com/xml/ns/persistence/persistence_2_0.xsd"
             version="2.0">
    <persistence-unit name="myapp" transaction-type="RESOURCE_LOCAL">
        <provider>org.hibernate.ejb.HibernatePersistence</provider>
        <properties>
            <property name="hibernate.connection.driver_class" value="org.h2.Driver"/>
            <property name="hibernate.connection.url" value="jdbc:h2:tcp://localhost/~/myapp"/>
            <property name="hibernate.connection.username" value="sa"/>
            <property name="hibernate.connection.password" value=""/>
            <property name="hibernate.dialect" value="org.hibernate.dialect.H2Dialect"/>
            <property name="hibernate.hbm2ddl.auto" value="create"/>
        </properties>
    </persistence-unit>
</persistence>

Creating the Wicket page

Here, you’ll create a Wicket page to display some products loaded from the database. So, create the main/scala folder, right click it and choose New | Scala Class. Name the class as MyPage and put it into the com.foo.myapp package. The class is shown below:


package com.foo.myapp

import org.apache.wicket.markup.html._
import basic.Label
import list.{ListItem, ListView}
import org.apache.wicket.spring.injection.annot.SpringBean
import org.apache.wicket.model.CompoundPropertyModel

class MyPage extends WebPage {
  @SpringBean
  var ps: ProductService = _
  val productListView = new ListView[Product]("productListView", ps.getAll) {
    def populateItem(item: ListItem[Product]) = {
      item.setModel(new CompoundPropertyModel[Product](item.getDefaultModelObject))
      item.add(new Label("name"))
      item.add(new Label("price"))
    }
  }
  add(productListView)
}

Note that it is using a ProductService object to load the products. You’ll create it later. In addition, note that the field is assigned to an underscore (_), which tells the Scala compile to NOT initialize, but leave it at the default state (null in this case). This is required for the injection to work. If you assign it to null explicitly, you
will overwrite the Spring bean as the injection will occur before the constructor of MyPage is executed.

Now, create the MyPage.html file in src/main/resources/com/foo/myapp:

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<html>
    <table border="1">
        <tr wicket:id="productListView">
            <td wicket:id="name"></td>
            <td wicket:id="price"></td>
        </tr>
    </table>
</html>

Creating the ProductService

Right click the com.foo.myapp package in the src/main/scala folder and choose New | Scala Class, then choose to create a trait named ProductService:

package com.foo.myapp

import java.util.List

trait ProductService {
  def getAll: List[Product]
}

This is the interface. To create the implementation, create a DefaultProductService Scala class in the same package:

package com.foo.myapp

import javax.persistence.{PersistenceContext, EntityManager}
import org.springframework.stereotype.{Service}
import org.springframework.transaction.annotation.Transactional
import org.springframework.beans.factory.annotation.Autowired

@Service
@Transactional
class DefaultProductService extends ProductService {
  @Autowired
  var products: Products = _

  def getAll = products.getAll
}

Note that it is using a DAO named of type Products to do the work.

Creating the Products DAO

To create the DAO, create a trait named Products in the same package:

package com.foo.myapp

import java.util.List

trait Products {
   def getAll: List[Product]
}

Then create the implementation Scala class DefaultProducts in the same package:

package com.foo.myapp

import javax.persistence.{PersistenceContext, EntityManager}
import org.springframework.stereotype.Repository
import java.util.List

@Repository
class DefaultProducts extends Products {
  @PersistenceContext
  var em: EntityManager = _

  def getAll = {
    em.createQuery("select p from Product p").getResultList.asInstanceOf[List[Product]]
  }
}

Creating the entity class

Create the Product class and map it to the database:

package com.foo.myapp

import javax.persistence.{GeneratedValue, Id, Entity}

@Entity
class Product {
  @Id
  @GeneratedValue
  var id: Long = _
  var name: String = _
  var price: Double = _
}

Creating the Wicket application class

Finally, create MyApp Scala class in the same package:


package com.foo.myapp

import org.apache.wicket.protocol.http.WebApplication
import com.ttdev.wicketpagetest.MockableSpringBeanInjector

class MyApp extends WebApplication {
  def getHomePage = classOf[MyPage]

  override def init = {
    MockableSpringBeanInjector.installInjector(this)
  }
}

Here you specify MyPage as the home page and install an injector that can inject Spring beans (as well as mock objects, even though you won’t use this capability here).

Running the application

As the Intellij IDEA community edition doesn’t include the integration with app servers, you’ll embed Jetty to run your application. This is easy. Just create a Scala class ManualTest in the com.foo.myapp package in the test/scala folder (NOT the main/scala folder). The content is below:

package com.foo.myapp

import com.ttdev.wicketpagetest.{WebAppJettyConfiguration, WicketAppJettyLauncher}

object ManualTest {
  def main(args: Array[String]) {
    val l = new WicketAppJettyLauncher
    l.startAppInJetty(new WebAppJettyConfiguration)
  }
}

To run the application, you need to have the H2 database server running first. So, go to http://www.h2database.com to download and unpack it. Then change into h2/bin and run h2.bat (or h2.sh on Linux).

While the ManualTest class is the active editor in IDEA, choose Run | Run in IDEA. It will run ManualTest which will launch Jetty to run your application.

To test it, try accessing http://localhost:8888/app in a browser. It should display nothing as there is no product in the database. However, it will create the table required.

To add some products, go to http://localhost:8082 to access the H2 web client. Enter jdbc:h2:tcp://localhost/~/myapp as the JDBC URL. Click Connect. Then issue the SQL statement:

SELECT * FROM PRODUCT

to select the product. Nothing should be there. That’s fine. In the result display, click the plus sign to create a product record. Feel free to add more.

Finally, reload http://localhost:8888/app and you should see the products displayed in the Wicket page.

Updated: You can download the project folder.

Three time-saving tips you didn’t know in testing Wicket pages

Thursday, June 17th, 2010

If you use the excellent Wicket framework to development web applications, with the Wicket Page Test library, the following tips will greatly reduce your turn-around time during development:

Tip 1: Install mock objects into your page so that it won’t hit the database during the unit test

For example, the page below usually will use a Spring bean to load a Product object from the database:

public class Page1 extends WebPage {
   @SpringBean
   private MyService service;

   public Page1(String productId) {
      Product p = service.loadProduct(productId);
      ...
   }
}

In the unit test, you can install a mock object to replace the service like:

@Test
public class Page1Test {
   private Product p1;

   public void test1() {
      p1 = new Product(...); //hard code Product
      MockableSpringBeanInjector.mockBean("service", new MyService() {
         public Product loadProduct(String productId) {
            return productId.equals("p1") ? p1 : null;
        }
      });
      DefaultSelenium selenium = ...;
      selenium.open(...); //open the page
      assert ...; //inspect the HTML elements
   }
}

Tip 2: Test real AJAX effect and wait for AJAX completion easily

You can, say, click an AJAX link or AJAX button and wait for the completion easily like:

@Test
public class Page1Test {
   public void test1() {
      Selenium selenium = ...;
      WicketSelenium ws = new WicketSelenium(selenium);
      ...
      selenium.click("link=Buy"); //click an AJAX link
      ws.waitUntilAjaxDone(); //wait until page is refreshed
      assert ...; //inspect the HTML elements
   }
}

Tip 3: Locate the HTML element generated by a given Wicket component easily

For example, to click the link generated by a Wicket Link component whose ID is “productDetails”, just the special wicket locator:

@Test
public class Page1Test {
   public void test1() {
      Selenium selenium = ...;
      ...
      //locate the link whose Wicket ID is productDetails
      selenium.click("wicket=//productDetails");
      selenium.waitForPageToLoad("3000");
      assert ...; //inspect the HTML elements
   }
}

You can even look further for a descendant component using something like //product//order (the order button for the product) or even use an index (if you have a loop) like //product[3]//order (the order button for the 3rd product).

To apply these tips, get the open source Wicket Page Test library. There is a step-by-step tutorial there.

Disclosure: I am the creator of Wicket Page Test.

Better approach to unit testing pages in web applications

Friday, November 13th, 2009

It’s common to see two approaches to unit testing pages in web applications: in-container (real container and browser) and out of container (mocked container and no browser). The former is good in that the tests can check the HTML DOM element and thus works with AJAX, but it is difficult to mock the services as the web application is running "on the other side". The latter is the opposite: Easy to mock the services but usually the tests can only check the internal state of the program, not the end HTML code.

In fact, a better approach is to combine them together: Run the application in the container and run the container in-process so that the test code can mock the services. This way you can control the right thing (user input on the web page and the data your pages get, from the services), and then observe the right thing (HTML DOM elements, possible manipulated by Javascript/AJAX). In order to run the container in-process, we can, eg, run an embedded Jetty. As it is run in the same process, the test code can access the servlet context and thus can get the opportunity to replace the services with mocks.

I’ve created a proof of concept library for unit testing Wicket pages. The mocking is done by using a chain of component injectors. The first one keeps a map of mock objects. The second one is the normal one to inject Spring beans. Usually the map in the first injector is empty so it has no practical effect in production. But your tests can put mock objects into there to perform mocking.

The library is released as open source software (LGPL). You’re welcome to check it out at http://wicketpagetest.sourceforge.net. There is a step-by-step tutorial.

Finally, potentially this approach can be applied to other web frameworks like JSF, Tapestry and etc.

My thoughts on the differences between Tapestry and Wicket

Wednesday, September 26th, 2007

After working with both Tapestry (3.x, 4.x and some alpha versions of T5 and as a committer) and Wicket (1.2x and 1.3 betas), below is the differences that I found:

Genius vs developer community

In Tapestry, it is Howard who writes most of the code and makes the final decision. There are a few committers but they are either working on T4 which is going to be superseded by T5 or just fixing bugs or writing documentation. I believe this is because Howard is such a genius in programming that few committer is up to a level to technically challenge him. In addition, as he wrote most of the code, naturally he gets the final say. But most importantly, I think it is because star programmers normally aren’t good project managers. When I was a committer in Tapestry, I always wanted Howard to take more on a role of articulating and sharing his vision of T5, inviting active code committers and reviewing their code instead of writing his own (like Linus does with Linux). It seemed that I didn’t succeed. Maybe he believes it takes less time to write code than relying on others to implement his vision, after all not many people are smart enough to understand his vision, not to mention to write high qualify code as he.

In Wicket, decisions are always made by technical discussions among the many active committers (8 or so) and then a vote. Those committers are active in the sense that they wrote and maintain significant parts of Wicket (e.g., core, unit testing framework, portlet, ajax). The creator of Wicket is no longer the most active code contributor. In fact I once saw him asking others on the mailing list how a part of Wicket worked!

Performance vs ease of use

A major design goal of Tapestry is high performance. In fact, Howard reserves T5 and Java for situations when high performance is required, while dynamic languages should be used for the rest. This design goal leads to the decision that 1) page objects should be pooled and are stateless as the state is maintained separately. 2) a component can generate multiple element in the output and therefore the component tree is vastly different from the element tree generated.

In Wicket, performance is not a design goal. Instead, it is the ease of use. This leads to the decision that 1) page objects are stateful and 2) there is a direct one-to-one mapping between the component tree and the element tree generated. These differences make it very easy to learn Wicket, to create Wicket pages and to understand how a Wicket application works, at the cost of heavier use of the session. In addition, this also supports the Back button transparently in Wicket as each page in the browser corresponds to a unique page object in the session. In Tapestry, one could use client persistence to store the state to achieve a similar effect. However, client side state is insecure by nature.

Expressive power vs conceptual familiarity

Just like other world class programmers, Howard favors the ultimate power of expression. That is, he hates any kind of duplication or any boileterplate code. Therefore, one can find a lot of domain specific languages in Tapestry and a lot of automatic magic running to free the programmer from any chore.

In Wicket, everything except the HTML code is done in Java. Plain old Java, not a domain specific language built on topic of Java annotations. There is more boilerplate code, but the programmer will get first class tool support (content assist), compile time checking, refactoring, API doc generation and access and etc. More importantly, it is a lot easier to learn because there is no new concept/language to learn (e.g., a listener in Tapestry is just a plain Java abstract method in Wicket, a validator in Tapestry is just a Java object in Wicket). The use of domain specific language also creates a barrier for functional enhancements. One has to build the function itself and then enhance the parser.

Code quality vs functionality

I’ve never seen any code that is as good quality as the Tapestry code. The Wicket code just won’t compare! One can easily find duplicate code, untestable code due to the extensive use of concrete classes instead of interface, classes having specific logic to handle some specific arguments if they along to a certain classes (fragile) and etc.

Is this a bad thing for Wicket? Yes in a sense. However, this is the result of having a lot of code contributors, including those who are not genius but have a strong itch and the ability to code a solution. So the other side of the story is that Wicket tend to have more working functions than Tapestry (tree component, more ajax functions, modal dialog, dynamic images, wizard. role based authorization, captcha, breadcrumb, …).

Technology driven vs user driven

Howard strives to create a framework that is technically excellent so to attract users. However, users can seldom persuade Howard on what is good for them. The Wicket team seems to listen to the users on what they want.

Technical advances vs version compatibility

I felt Howard, as a technical genius, has an unstoppable urge and unlimited energy to improve the Tapstry code. Therefore, whenever he sees a technical advance that can improve the code quality, he can’t wait to adopt it (T3=>T4 using IoC; T4=>T5 using annotations replacing for XML and convention over configuration), hoping to deliver an excellent shiny new framework for users to enjoy, while leaving the old incompatible version behind.

The Wicket team on the other hand favors version compatibility more than technical advances.

Final words

So, which is better for you? It’s up to you to decide. I’ve written a book on Wicket and another one Tapestry 4.1. The first several chapters in both books are free, so you can check them out yourself.