November 6, 2011

How to set Wicket up with Tomcat

The Wicket quickstart project is great, but it can be confusing to progress from that embedded Jetty server to Tomcat (or any other more stable setup), especially if it's your first time setting up a web application. Here's how to make it happen using nothing but ant. (If you can't get the quickstart project working, see my earlier post first.)

The first step is making sure you have all your tools. You're going to need recent versions of both Tomcat and Apache ant already installed.

Just as important as your tools is your directory structure. There is some room for individuality here, but the structure I'm going to show you follows fairly standard conventions. I didn't include version numbers with the JAR files because they'll probably have changed by the time you read this anyways.

\WicketTomcat
  +---src
  | +---main
  | | +---java
  | | | \---com
  | | |   \---HelloWicket
  | | |         HelloWorld.java
  | | |         HelloWorld.html
  | | |         HelloWorldApplication.java
  | | \---webapp
  | |   \---WEB-INF
  | |         web.xml
  | \---test
  |   \---java
  +---lib
  |     junit.jar
  |     log4j.jar
  |     servlet-api.jar
  |     slf4j-api.jar
  |     slf4j-log4j.jar
  |     wicket.jar
  |     wicket-extensions.jar
  +---target
    build.xml

Now that you have all the pieces in place, all you have to do is fill in the files' contents. I happened to use Eclipse Indigo, but there is absolutely nothing IDE-specific about these instructions. You can use any IDE or text editor you want.

HelloWorld.java:

package com.HelloWicket;

import org.apache.wicket.markup.html.WebPage;
import org.apache.wicket.markup.html.basic.Label;

public class HelloWorld extends WebPage {
public HelloWorld() {
add(new Label("message", "Hello, Wicket!"));
}
}


HelloWorld.html

<html>
<head>
<title>Wicket Tomcat test title</title>
</head>
<body>
<span wicket:id="message">Message goes here</span>
</body>
</html>


HelloWorldApplication.java

package com.HelloWicket;

import org.apache.wicket.Page;
import org.apache.wicket.protocol.http.WebApplication;

public class HelloWorldApplication extends WebApplication {
public HelloWorldApplication() {
}

/**
* @see org.apache.wicket.Application#getHomePage()
*/
@Override
public Class getHomePage() {
return HelloWorld.class;
}
}


web.xml

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE web-app
PUBLIC "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
"http://java.sun.com/dtd/web-app_2_3.dtd">
<web-app>
<display-name>Extremely simple example of deploying Wicket on Tomcat</display-name>
<context-param>
<param-name>configuration</param-name>
<param-value>development</param-value> <!-- Wicket mode (development or deployment) -->
</context-param>
<filter>
<filter-name>HelloWicket</filter-name> <!-- To be used in filter-mapping > filter-name below -->
<filter-class>
org.apache.wicket.protocol.http.WicketFilter
</filter-class>
<init-param>
<param-name>applicationClassName</param-name>
<param-value>
com.HelloWicket.HelloWorldApplication <!-- Fully qualified name of WebApplication class -->
</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>HelloWicket</filter-name> <!-- Must match filter > filter-name above -->
<url-pattern>/*</url-pattern> <!-- Take control of all URLs that start with http://localhost:8080/HelloWicket/ -->
</filter-mapping>
</web-app>
<!--
After deploying to Tomcat, access with http://localhost:8080/HelloWicket/.

Source: http://wicket.apache.org/learn/examples/helloworld.html
-->


build.xml

<?xml version="1.0" encoding="UTF-8"?>
<project default="war" name="HelloWicket" basedir=".">
<property name="final.name" value="HelloWicket" />
<property name="src.main.dir" value="src/main/java" />
<property name="src.test.dir" value="src/test/java" />
<property name="src.web.dir" value="src/main/webapp" />
<property name="lib.dir" value="lib" />
<property name="build.dir" value="target" />
<property name="build.main.classes" value="${build.dir}/classes" />
<property name="build.test.classes" value="${build.dir}/test-classes" />
<property name="build.test.reports" value="${build.dir}/test-reports" />
<property name="build.reports.dir" value="${build.dir}/reports" />
<property name="tomcat.dir" value="..\..\..\..\Program Files\Apache Software Foundation\apache-tomcat-7.0.22\webapps" />

<path id="build.classpath">
<fileset dir="${lib.dir}">
<include name="**/*.jar" />
</fileset>
</path>
<target name="clean">
<delete dir="${build.dir}" failonerror="false" />
<delete file="${final.name}.war" failonerror="false" />
</target>
<target name="init">
<mkdir dir="${build.dir}" />
</target>
<target name="compile" depends="init">
<mkdir dir="${build.main.classes}" />
<javac destdir="${build.main.classes}" target="1.6" source="1.6" srcdir="${src.main.dir}" classpathref="build.classpath" includeantruntime="false" />
<copy todir="${build.main.classes}">
<fileset dir="${src.main.dir}">
<include name="**/*.*" />
<exclude name="**/*.java" />
</fileset>
</copy>
</target>
<target name="test-compile" depends="compile">
<mkdir dir="${build.test.classes}" />
<javac destdir="${build.test.classes}" target="1.6" source="1.6" srcdir="${src.test.dir}" includeantruntime="false">
<classpath>
<path refid="build.classpath" />
<pathelement path="${build.main.classes}" />
</classpath>
</javac>
<copy todir="${build.test.classes}">
<fileset dir="${src.test.dir}">
<include name="**/*.*" />
<exclude name="**/*.java" />
</fileset>
</copy>
</target>
<target name="test" depends="test-compile">
<mkdir dir="${build.test.reports}" />
<junit dir="./" failureproperty="test.failure" printSummary="yes" fork="true" haltonerror="true">
<sysproperty key="basedir" value="." />
<formatter type="xml" />
<classpath>
<path refid="build.classpath" />
<pathelement path="${build.main.classes}" />
<pathelement path="${build.test.classes}" />
</classpath>
<batchtest todir="${build.test.reports}">
<fileset dir="${src.test.dir}">
<include name="**/*Test*.java" />
</fileset>
</batchtest>
</junit>
<mkdir dir="${build.reports.dir}" />
<junitreport todir="${build.reports.dir}">
<fileset dir="${build.test.reports}">
<include name="TEST-*.xml" />
</fileset>
<report format="frames" todir="${build.reports.dir}" />
</junitreport>
</target>
<target name="war" depends="test">
<war destfile="${build.dir}/${final.name}.war" webxml="${src.web.dir}/WEB-INF/web.xml">
<lib dir="lib">
<include name="wicket*.jar" />
<include name="slf4j*.jar" />
<include name="log4j*.jar" />
<include name="servlet*.jar" />
</lib>
<classes dir="${build.main.classes}" />
<fileset dir="${src.web.dir}">
<include name="**/*" />
<exclude name="**/web.xml" />
</fileset>
</war>
</target>

<target name="deploy" depends="war">
<echo>Deploying .war to local Tomcat</echo>
<copy todir="${tomcat.dir}">
<fileset dir="${build.dir}" includes="${final.name}.war" />
</copy>
</target>
</project>


Simply adjust for your local Tomcat install's webapps directory, and you should be all set!

Sources:
Chapter 15 of Wicket in Action, free to download from the book's website
The Wicket website's quickstart page and Hello World example page

February 10, 2011

Simple sample to create a dynamic flash card layout in Swing

  • Solid white background
  • Two dynamically resizing text fields
  • Buttons have equal width
  • Buttons float to the top, no more weird grid-based spacing issues
  • Text areas are scrollable
  • Starts out small, but window can be adjusted to any reasonable size, even maximized
package migLayoutCenterExpandTest;

import java.awt.Color;
import java.awt.Container;
import java.awt.Dimension;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;

import javax.swing.BorderFactory;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JTextArea;

import net.miginfocom.swing.MigLayout;

public class Main {
    static JFrame      frame;

    static JPanel      firstPanel;
    static JPanel      secondPanel;

    static JLabel      firstPanelLabel;
    static JButton     firstPanelButton;

    static JTextArea   textArea1;
    static JTextArea   textArea2;
    static JScrollPane textScrollPane1;
    static JScrollPane textScrollPane2;
    static JButton     button1;
    static JButton     button2;
    static JButton     button3;
    static JButton     button4;
    static JButton     button5;
    static JButton     button6;

    /**
     * @param args
     */
    public static void main(String[] args) {
        createObjects();

        setUpFrame();
        setUpFirstPanel();
        setUpSecondPanel();

        frame.getContentPane().add(firstPanel);
        frame.setVisible(true);
    }

    public static void createObjects() {
        frame = new JFrame();

        firstPanel = new JPanel();

        firstPanelLabel = new JLabel("first label");
        firstPanelButton = new JButton("change screens");

        secondPanel = new JPanel();

        textArea1 = new JTextArea("Text area 1");
        textArea2 = new JTextArea("Text area 2");
        textScrollPane1 = new JScrollPane();
        textScrollPane2 = new JScrollPane();
        button1 = new JButton("Button one text");
        button2 = new JButton("Button two text");
        button3 = new JButton("Button three text");
        button4 = new JButton("Button four text");
        button5 = new JButton("Button five text");
        button6 = new JButton("Button six text");
    }

    public static void setUpFrame() {
        frame.setSize(400, 300);

        JPanel contentPane = (JPanel) frame.getContentPane();
        contentPane.setLayout(new MigLayout("align 50% 50%, filly"));
        contentPane.setBackground(Color.WHITE); // Disable this to debug panel sizes

        frame.addWindowListener(new ExitListener());
    }

    public static void setUpFirstPanel() {
        firstPanel.setLayout(new MigLayout("flowy"));
        firstPanel.setBackground(Color.WHITE);

        firstPanel.add(firstPanelLabel, "align 50%");
        firstPanel.add(firstPanelButton, "align 50%");

        firstPanelButton.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {
                Container contentPane = frame.getContentPane();
                contentPane.removeAll(); // Required for new stuff to appear
                contentPane.add(secondPanel); // Required for new stuff to appear; this _is_ the new stuff, after all
                ((JPanel) contentPane).revalidate(); // Required for new stuff to appear
                contentPane.repaint(); // Without this, old stuff isn't removed before new stuff is painted and is still visible underneath
            }
        });
    }

    public static void setUpSecondPanel() {
        secondPanel.setLayout(new MigLayout("fill, flowy"));
        secondPanel.setBackground(Color.WHITE);

        secondPanel.setPreferredSize(new Dimension(1650, 1080));
        secondPanel.setMinimumSize(new Dimension(300, 200));
        secondPanel.setMaximumSize(new Dimension(1920, 1080));

        textScrollPane1 = new JScrollPane(textArea1);
        textScrollPane2 = new JScrollPane(textArea2);

        textScrollPane1.setBackground(Color.WHITE);
        textScrollPane2.setBackground(Color.WHITE);

        textScrollPane1.setBorder(BorderFactory.createTitledBorder("Text area 1"));
        textScrollPane2.setBorder(BorderFactory.createTitledBorder("Text area 2"));

        textScrollPane1.setPreferredSize(new Dimension(400, 300));
        textScrollPane2.setPreferredSize(new Dimension(400, 300));

        // cell col row [span x [span y]]
        secondPanel.add(textScrollPane1, "cell 0 0 1 1, push, grow");
        secondPanel.add(textScrollPane2, "cell 0 1 1 1, push, grow");
        secondPanel.add(button1, "cell 1 0 1 2, aligny top, growx");
        secondPanel.add(button2, "cell 1 0, growx");
        secondPanel.add(button3, "cell 1 0, growx");
        secondPanel.add(button4, "cell 1 0, growx");
        secondPanel.add(button5, "cell 1 0, growx");
        secondPanel.add(button6, "cell 1 0, growx");
    }
}

class ExitListener extends WindowAdapter {
    public void windowClosing(WindowEvent event) {
        System.exit(0);
    }
}

February 4, 2011

Simple sample to replace the contents of a content pane in Swing


package swingTest;

import java.awt.Container;
import java.awt.FlowLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;

import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;

public class Main
{
static JFrame frame;

static JPanel firstPanel;
static JPanel secondPanel;

static JLabel firstPanelLabel;
static JButton firstPanelButton;

static JLabel secondPanelLabel;

/**
* @param args
*/
public static void main(String[] args)
{
createObjects();

firstPanel.add(firstPanelLabel);
firstPanel.add(firstPanelButton);

firstPanelButton.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e)
{
Container contentPane = frame.getContentPane();
contentPane.remove(firstPanel); // Required for new stuff to appear
contentPane.add(secondPanel); // Required for new stuff to appear; this _is_ the new stuff, after all
((JPanel) contentPane).revalidate(); // Required for new stuff to appear
contentPane.repaint(); // Without this, old stuff isn't removed before new stuff is painted and is still visible underneath
}
});

secondPanel.add(secondPanelLabel);

frame.addWindowListener(new ExitListener());
frame.getContentPane().add(firstPanel);
frame.setVisible(true);
}

public static void createObjects()
{
frame = new JFrame();
frame.setSize(500, 500);

firstPanel = new JPanel();
firstPanel.setLayout(new FlowLayout());
firstPanelLabel = new JLabel("first label");
firstPanelButton = new JButton("first button");

secondPanel = new JPanel();
secondPanel.setLayout(new FlowLayout());
secondPanelLabel = new JLabel("second label");
}
}

class ExitListener extends WindowAdapter
{
public void windowClosing(WindowEvent event)
{
System.exit(0);
}
}

December 3, 2010

Wicket installation

Update: I finally got around to writing the second part, about setting Wicket up with Tomcat instead of relying solely on the embedded Jetty server. See it here.


Downloaded JDK 6_22
Downloaded Maven 3.01
Installed JDK to C:\Program Files\Java\jdk1.6.0_22
Unzipped maven to C:\Program Files\apache-maven-3.0.1
Updated system environment variables:

- M2
C:\Program Files\apache-maven-3.0.1
- M2_HOME
%M2_HOME%\bin = C:\Program Files\apache-maven-3.0.1\bin
- JAVA_HOME
C:\Program Files\Java\jdk1.6.0_22
- M2_REPO
C:\Users\Programmer\.m2\repository

Followed instructions at Wicket quickstart
Generated this Maven command from quickstart:

mvn archetype:create -DarchetypeGroupId=org.apache.wicket -DarchetypeArtifactId=wicket-archetype-quickstart -DarchetypeVersion=1.4.1 -DgroupId=com.mycompany -DartifactId=projName

Ran above Maven command from command line
Relevant files, including Wicket source, were downloaded automatically (based on POM?)
Ran mvn eclipse:eclipse to create an Eclipse project based on above
Imported project into Eclipse with File > Import..., Existing Projects

Ran Start.java in the test folder and went to http://localhost:8080 — success.

Wanted to add Wicket Extensions support.
Manually adding JAR file to to M2_REPO directory didn't work.

Current status:
Adding a dependency to the POM is probably right (there's even a similar dependency in there but commented out), but how will Maven understand that the new dependency needs to be added? Merely uncommenting and running Start does nothing.

Resolved: Just run mvn clean dependency:copy-dependencies after updating the POM. There's probably a better way to do this, though. Then configure the build path by using Add Variables... (not Add JARs), select M2_REPO, press Extend, find the desired JAR (in this case, Wicket Extensions).

Current status:
Need to figure out how to make this worth with Tomcat instead of relying on the embedded Jetty server all the time.

August 23, 2010

Why arrow code is bad

I, like Jeff Atwood, don't like arrow code.

But is arrow code actually bad, or just unsightly? It's bad. Jeff mentions cyclomatic complexity but doesn't describe it; so see Wikipedia.

In particular,

[Most of the relevant] studies find a strong positive correlation between cyclomatic complexity and defects: modules that have the highest complexity tend to also contain the most defects.

August 10, 2010

How Ryan Tomayko explained REST to his wife

The web is built on an architectural style called REST. REST provides a definition of a resource, which is what [URLs] point to.

A web page is a representation of a resource. Resources are just concepts. [URLs] tell the browser that there’s a concept somewhere. A browser can then go ask for a specific representation of the concept. Specifically, the browser asks for the web page representation of the concept.

Full post on Ryan's blog

July 13, 2010

Vista fonts compared to older fonts

The "Vista fonts" are Cambria, Calibri, Candara, Consolas, Constantia and Corbel. A good piece on them is provided by hunlock.com here.