Build rich Java Web applications with Apache Wink and Ajax

This article introduces Apache Wink, which is a framework for building Representational State Transfer (REST)ful Web services. Apache Wink is an Apache Incubator project. Its goal is to provide an easier method of writing RESTful Web services by providing the ability to use Java™ annotations to define services inside classes.

Share:

Nathan A. Good, Senior Consultant and Freelance Developer, Freelance Developer

Nathan GoodNathan A. Good lives in the Twin Cities area of Minnesota. Professionally, he does software development, software architecture, and systems administration. When he's not writing software, he enjoys building PCs and servers, reading about and working with new technologies, and trying to get his friends to make the move to open source software. He's written and co-written many books and articles, including Professional Red Hat Enterprise Linux 3, Regular Expression Recipes: A Problem-Solution Approach, and Foundations of PEAR: Rapid PHP Development.



16 February 2010

Also available in Chinese Japanese

This article introduces Apache Wink, demonstrates how to install it, and provides an example of a simple RESTful Web service that manages a task list. The example in this article is loosely based on the Bookmarks example that comes with the Apache Wink distribution. However, the example service in this article uses a different message format, one that is easily called from Web pages using Asynchronous JavaScript + XML (Ajax) for a richer user experience.

REST and Ajax

RESTful Web services are services that are exposed using a certain URL structure, providing a simplified interface for doing create, read, update, and delete (CRUD) operations with Web services. RESTful Web services can consume and provide messages in a variety of different formats identified by Multipurpose Internet Mail Extensions (MIME) types, including JavaScript Object Notation (JSON), XML, and other binary data.

The simple nature of the RESTful Web services makes accessing them from a Web client technology such as Ajax much simpler. The ability to use JSON as a message format further simplifies the interaction.

REST

Unlike SOAP, RESTful Web services do not necessarily require an XML document as the payload for messaging. There is no standard XML Schema Definition (XSD) that describes the way that messages must be formed. This allows RESTful Web services to be as simple as a document that is returned when a URL is accessed. In fact, a Web page does fit the profile of a RESTful Web service.

Because XML is not required for the message format, nearly any type of message content is supported. For instance, you can post plain text to a URL to call a POST method on a RESTful Web service. You can also use other simple message formats, such as JSON. Apache Wink matches the MIME type identified in the HTTP operation with service methods that consume and provide the given MIME types.

RESTful Web services use the different HTTP operations to expose services that do different operations. Although there is no defined standard per se, there are some guidelines that define which HTTP operations should be used for certain tasks. See Table 1.

Table 1. REST operations and URL examples
HTTP operationSample URLPurpose
GEThttp://localhost:8080/Task/rest/tasksLists all of the tasks found by the service
GEThttp://localhost:8080/Task/rest/tasks/1Gets the task with an ID of 1
POSThttp://localhost:8080/Task/rest/tasksCreates a new task with the posted data
PUThttp://localhost:8080/Task/rest/tasks/1Updates the specified task with the ID of 1 using the data request
DELETEhttp://localhost:8080/Task/rest/tasks/1Deletes the task with the ID of 1

Ajax

In the last few years, Ajax has become a much more commonplace Web technology, providing a relatively rich user experience and allowing standard Web pages to use data more effectively. With Ajax, Web pages can process data without having to refresh the entire page in the browser, which provides a much smoother interface for the user.

Ajax is simply the use of JavaScript and XML objects available to the Web browser used in a Web page to perform operations. A JavaScript function that uses Ajax accesses a URL and is capable of doing something with the response. This interaction is ideal for calling RESTful Web services from Ajax because the JavaScript methods simply access the RESTful Web service URL as if it is a normal Web page and interpret the results.

The example in this article uses the jQuery JavaScript library to access the RESTful Web services with Ajax.


Installing Wink

The binary distribution of Apache Wink contains libraries that you include in your dynamic Web application project. Installation of Apache Wink is a matter of downloading the binary distribution and importing the libraries into a new dynamic Web project, then using the classes that are in the Apache Wink libraries to annotate your Java classes and methods.

To build the RESTful Web service in this article, you need the server libraries. Apache Wink also includes client libraries that you can use to call RESTful Web services from Java code, but you don't need those in this project.

Start by downloading the Apache Wink binary distribution (see Resources) to a location on your computer. Extract the libraries to a directory so you can import the necessary libraries into your new dynamic Web project.


A sample Wink service

To build the sample Web service in Eclipse, follow these steps:

  1. Open Eclipse and select New > Project from the Eclipse menu. From the New Project screen, select Dynamic Web Project under Web and click Next.
  2. Enter a project name and select the target server runtime. The example in this article uses the IBM® WebSphere® Application Server Community Edition (Community Edition) v2.1 server runtime. If it's not installed in your instance of Eclipse, click New to add it (see Figure 1). Click Next after you select the target runtime.
    Figure 1. Creating the dynamic Web project
    A window showing WASCE 2.1 as the target runtime.
  3. Click Next to keep the default source and output folders.
  4. Type a context root (for example, Tasks) and keep WebContent as the content directory. Select Generate web.xml deployment descriptor, and click Next or Finish.
  5. If you're using the Community Edition v2.1 runtime, the next window includes information that is put into the Geronimo deployment plan file (see Figure 2). Enter the group ID (for example, com.example.group), artifact ID (such as tasks), and version number (such as 1.0.0). Click Finish when you're done.
    Figure 2. Entering the Geronimo deployment plan information
    A window showing the Geronimo deployment information.

Now that you've created the project, use the following steps to import the Apache Wink libraries and other dependencies into the project:

  1. Click on the project and select Import from the context menu. Select File System and click Next.
  2. Navigate to the location where you extracted the Apache Wink libraries and open the dist folder. Import the following Java Archive (JAR) files into the dist folder:
    • wink-1.0-incubating.jar
    • wink-common-1.0-incubating.jar
    • wink-server-1.0-incubating.jar
  3. After you import the Apache Wink libraries, import the following dependencies from the lib folder found in the binary distribution:
    • activation-1.1.jar
    • commons-lang-2.3.jar
    • jaxb-api-2.1.jar
    • jaxb-impl-2.1.4.jar
    • jsr311-api-1.0.jar
    • slf4j-api-1.5.8.jar
    • slf4j-simple-1.5.8.jar
    • stax-api-1.0-2.jar
  4. Finally, you need to import the JAR files that include the JSON providers, because the example service produces data in the JSON format. Include the JAR files from the ext/wink-json-provider directory in the Apache Wink distribution: wink-json-provider-1.0-incubating.jar and json-20080701.jar.

Now that you've imported the necessary libraries, you're nearly ready to write your first RESTful Web service. But first, update the web.xml file to include the RestServlet from Apache Wink that is responsible for routing the requests to the correct resources.

See the web.xml example in Listing 1.

Listing 1. The web.xml file
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns="http://java.sun.com/xml/ns/javaee" 
    xmlns:web="http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"
    xsi:schemaLocation="http://java.sun.com/xml/ns/javaee 
    http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"
    id="WebApp_ID" version="2.5">
    <display-name>Tasks</display-name>
    <description>REST services for managing tasks.</description>

    <servlet>
        <servlet-name>restService</servlet-name>
        <servlet-class>org.apache.wink.server.internal.servlet.RestServlet</servlet-class>

        <init-param>
            <param-name>applicationConfigLocation</param-name>
            <param-value>/WEB-INF/resources</param-value>
        </init-param>

        <init-param>
            <param-name>propertiesLocation</param-name>
            <param-value>/WEB-INF/tasks.properties</param-value>
        </init-param>

    </servlet>

    <servlet-mapping>
        <servlet-name>restService</servlet-name>
        <url-pattern>/rest/*</url-pattern>
    </servlet-mapping>

</web-app>

The web.xml file contains a few parameters that are used to initialize the servlet class, which is org.apache.wink.server.internal.servlet.RestServlet. The first is the applicationConfigLocation, which is the name of the file that includes the fully qualified class names of the resources that are used to perform the work in your services.

The second is the propertiesLocation parameter, which includes the name of the file that has properties used by the RestServlet class (see Resources for a full list of these properties).

The propertiesLocation in the web.xml file points to the WEB-INF/tasks.properties location. Create a file in the WEB-INF directory called tasks.properties and include the settings shown in Listing 2.

Listing 2. The tasks.properties file
wink.defaultUrisRelative=false

Now that your web.xml file is modified to load the RestServlet, you can create a resource that is called by the servlet when a matching URL is used.

To create a resource, simply create a Java class called TasksResource. Add the class annotation and create methods for handling the different REST operations using annotations, as shown in Listing 3.

Listing 3. The TasksResource class without implementation
package com.nathanagood.services;

import javax.ws.rs.Consumes;
import javax.ws.rs.DELETE;
import javax.ws.rs.GET;
import javax.ws.rs.POST;
import javax.ws.rs.PUT;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
import javax.ws.rs.Produces;
import javax.ws.rs.WebApplicationException;
import javax.ws.rs.core.Context;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.MultivaluedMap;
import javax.ws.rs.core.Response;
import javax.ws.rs.core.UriInfo;

import org.apache.wink.server.utils.LinkBuilders;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;

@Path("tasks")
public class TasksResource {

    private static final String TASK = "tasks";
    private static final String ITEM_PATH = "{tasks}";
    private static final String ENCODING = "UTF-8";
    
    @GET
    @Produces( { MediaType.APPLICATION_JSON })
    public JSONArray getTasks() {
        // TODO:  Add implementation
        return null;
    }

    @Path(ITEM_PATH)
    @GET
    @Produces( { MediaType.APPLICATION_JSON })
    public JSONObject getTask(@Context LinkBuilders link, @Context UriInfo uri,
            @PathParam(TASK) String taskId) {
        // TODO:  Add implementation
        return null;
    }

    @POST
    @Consumes( { MediaType.APPLICATION_FORM_URLENCODED })
    @Produces( { MediaType.APPLICATION_JSON })
    public JSONObject createTask(MultivaluedMap<String, String> formData,
            @Context UriInfo uriInfo, @Context LinkBuilders linksBuilders) {
        // TODO:  Add implementation
        return null;
    }

    @Path(ITEM_PATH)
    @PUT
    @Consumes( { MediaType.APPLICATION_JSON })
    @Produces( { MediaType.APPLICATION_JSON })
    public JSONObject updateTask(JSONObject task, @Context UriInfo uriInfo,
            @Context LinkBuilders linksBuilders) {
        // TODO: Add implementation
        return null;
    }

    @Path(ITEM_PATH)
    @DELETE
    @Produces( { MediaType.APPLICATION_JSON })
    public JSONObject deleteTask(@Context LinkBuilders link, @Context UriInfo uri,
            @PathParam(TASK) String taskId) {
        // TODO: Add implementation
        return null;
    }
}

The GET, POST, PUT, and DELETE Java annotations map directly to the type of HTTP operation that is used to access the service by the client. The Apache Wink servlet routes the request to the correct method based on the HTTP operation. In addition to using the HTTP operation, Apache Wink uses the MIME type of the HTTP request to map the request to the correct method. The Consumes and Produces annotations provide a way to define the MIME types that are handled by the methods. Note that they all return the JSON MIME type (MediaType.APPLICATION_JSON). The POST method consumes the MediaType.APPLICATION_FORM_URLENCODED type, which is important later because the Ajax method used to post to the service URL uses that content type.

Adding implementation

So far, the methods are mostly empty methods. For brevity, this article demonstrates how to implement the two methods that handle the different GET operations and the method that handles the POST operation. The getTasks() and getTask() methods allow you to get a listing of all the tasks and each individual task by its ID, respectively. The createTask() method lets you create a new task in the data store with the Ajax client.

Before adding implementation code to the resource, create a few classes that provide enough implementation to test the functions. A Task class contains information about the tasks that are stored in the task list (see Listing 4). You can create the class quickly by using Eclipse's features for creating accessors and constructors from fields.

Listing 4. The Task class
package com.nathanagood.services;

public class Task {

    private boolean complete;
    private String notes;
    private String dueDate;
    private String id;
    private String name;

    public Task(String id, String name, String notes, String dueDate) {
        super();
        this.id = id;
        this.name = name;
        this.notes = notes;
        this.dueDate = dueDate;
    }

    public String getNotes() {
        return notes;
    }

    public String getDueDate() {
        return dueDate;
    }

    public String getId() {
        return id;
    }

    public String getName() {
        return name;
    }

    public boolean isComplete() {
        return complete;
    }

    public void setComplete(boolean complete) {
        this.complete = complete;
    }

    public void setNotes(String description) {
        this.notes = description;
    }

    public void setDueDate(String dueDate) {
        this.dueDate = dueDate;
    }

    public void setId(String id) {
        this.id = id;
    }

    public void setName(String name) {
        this.name = name;
    }

}

Create a TaskManager class that is responsible for getting and storing the Task objects, as shown in Listing 5.

Listing 5. The TaskManager class
package com.nathanagood.services;

import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;

public class TaskManager {

    private static TaskManager instance = new TaskManager();
    private static final Map<String, Task> tasks = Collections
            .synchronizedMap(new HashMap<String, Task>());

    /* Initialize some tasks with hard-coded values */
    static {
        tasks.put("1", new Task("1", "Walk the dog", "Remember the leash", "Today"));
        tasks.put("2", new Task("2", "Go to the store", 
            "Buy milk and eggs", "Apr 1, 2010"));
    };

    private TaskManager() {
    }
    
    public static TaskManager getInstance() {
        return instance;
    }
    
    public Collection<Task> getTasks() {
        return Collections.unmodifiableCollection(tasks.values());
    }
    
    public Task getTask(String id) {
        return tasks.get(id);
    }
    
    public void addTask(String id, Task task)
    {
        tasks.put(id, task);
    }

}

Now that you have objects to work with, add some implementation code to the TaskResource class's getTasks(), getTask(), and createTask() methods, as shown in Listing 6.

Listing 6. The TaskResource method implementation
// imports snipped
@Path("tasks")
public class TasksResource {

    private static final String TASK = "tasks";
    private static final String ITEM_PATH = "{tasks}";
    private static final String ENCODING = "UTF-8";

    private Task createTask(MultivaluedMap<String, String> 
        formData) throws UnsupportedEncodingException {

        String id = formData.getFirst("id");
        String name = URLDecoder.decode(formData.getFirst("name"), ENCODING);
        String dueDate = URLDecoder.decode(formData.getFirst("dueDate"), ENCODING);
        String notes = URLDecoder.decode(formData.getFirst("notes"), ENCODING);
        boolean complete = Boolean.parseBoolean(formData.getFirst("complete"));

        Task task = new Task(id, name, notes, dueDate);
        task.setComplete(complete);

        return task;
    }

    private JSONObject createJSONObject(Task task) throws JSONException {
        JSONObject obj = new JSONObject();
        obj.put("name", task.getName());
        obj.put("notes", task.getNotes());
        obj.put("dueDate", task.getDueDate());
        obj.put("complete", task.isComplete());
        obj.put("id", task.getId());
        return obj;
    }

    @GET
    @Produces( { MediaType.APPLICATION_JSON })
    public JSONArray getTasks() {
        JSONArray result = new JSONArray();

        Collection<Task> tasks = TaskManager.getInstance().getTasks();

        for (Task task : tasks) {
            try {
                result.put(createJSONObject(task));
            } catch (JSONException e) {
                e.printStackTrace();
                throw new WebApplicationException(Response.Status.INTERNAL_SERVER_ERROR);
            }
        }

        return result;
    }

    @Path(ITEM_PATH)
    @GET
    @Produces( { MediaType.APPLICATION_JSON })
    public JSONObject getTask(@Context LinkBuilders link, @Context UriInfo uri,
            @PathParam(TASK) String taskId) {

        Task task = TaskManager.getInstance().getTask(taskId);

        if (task == null) {
            throw new WebApplicationException(Response.Status.NOT_FOUND);
        }

        JSONObject result;
        try {
            result = createJSONObject(task);
        } catch (JSONException e) {
            e.printStackTrace();
            throw new WebApplicationException(Response.Status.INTERNAL_SERVER_ERROR);
        }
        return result;
    }

    @POST
    @Consumes( { MediaType.APPLICATION_FORM_URLENCODED })
    @Produces( { MediaType.APPLICATION_JSON })
    public JSONObject createTask(MultivaluedMap<String, String> formData,
            @Context UriInfo uriInfo, @Context LinkBuilders linksBuilders) {

        Task newTask;
        JSONObject obj;

        try {
            
            newTask = createTask(formData);
            TaskManager.getInstance().addTask(newTask.getId(), newTask);
            obj = createJSONObject(newTask);
            
        } catch (Exception e) {
            e.printStackTrace();
            throw new WebApplicationException(Response.Status.INTERNAL_SERVER_ERROR);
        }

        return obj;

    }

// snipped
}

The methods shown here use the two additional classes created earlier—TaskManager and Task. The TaskManager class has a Java Collection of Task objects in it. In a real production scenario, this internal collection would be replaced by a service that loads the data from a database and translates it into Task objects.

After adding these methods, you are almost ready to see the REST service in action. Add the fully qualified Java class name of the TaskResource class to the WEB-INF/resources file (see Listing 7).

Listing 7. The WEB-INF/resources file
com.nathanagood.services.TasksResource

To test the service, first export the project as a Web Archive (WAR) file that is then deployed to the Community Edition instance. Click File > Export and choose Web\WAR File. Choose a destination for the file and click Finish, as shown in Figure 3.

Figure 3. Exporting the WAR file
A window showing the destination of the WAR file as /home/auser/Desktop/Tasks.war.

Now deploy the WAR file to Community Edition. The easiest way to deploy the application is through the Administrative Console.

Access the Administrative Console by going to the URL of the Community Edition instance (for example, http://localhost:8080/) and click on Administrative Console (see Figure 4). Log in to the console. If you haven't changed the user name or password from the default, log in using system as the username and manager as the password.

Figure 4. The Administrative Console link
A corner of a screen, which shows the Administrative Console link.

After you're logged in, click the Deploy New Applications link (see Figure 5).

Figure 5. The Deploy New Applications link
A corner of a screen, which shows the Deploy New Applications link.

Click Browse to choose the WAR file that you exported from your project, and click Install. If the installation of the WAR file was successful, you see messages like those shown in Figure 6.

Figure 6. Successful deployment messages
A message that shows the application was successfully deployed and started.

Now that the Web application is successfully deployed, you can see the JSON representation of the tasks by accessing the following URL from your Web browser: http://localhost:8080/Tasks/rest/tasks. To see one of the individual tasks represented as JSON, append the ID of the task (for example, http://localhost:8080/Tasks/rest/tasks/1). The ability to test the services simply by accessing a URL from a browser is another powerful advantage of RESTful Web services.

The JSON returned by the http://localhost:8080/Tasks/rest/tasks URL looks similar to that in Listing 8.

Listing 8. The example JSON output
[
  {
    "complete": false,
    "dueDate": "Apr 1, 2010",
    "id": "2",
    "name": "Go to the store",
    "notes": "Buy milk and eggs"
  },
  {
    "complete": false,
    "dueDate": "Today",
    "id": "1",
    "name": "Walk the dog",
    "notes": "Remember the leash"
  }
]

The Ajax client

Now that you've verified that the new RESTful Web service is up and running properly, you are ready to use Ajax from a Web page to display the data. To make the Ajax call easier to implement, use a library that handles the details of the Ajax call, such as the jQuery JavaScript library (see Resources).

Create a new directory called scripts inside the WebContent directory. Download the jQuery library, which is a single JavaScript file, and import it into the dynamic Web project in the WebContent/scripts directory.


Making the Ajax call

The jQuery library includes a method specifically for getting JSON data from a URL, which is perfect to use for calling the RESTful Web service GET methods (see Listing 9).

Listing 9. The jQuery.getJSON function
$.getJSON(url, [data], [callback]);

The jQuery library also includes the post method, which you can use to post data to the RESTful Web service. The jQuery library posts the data as the application/x-www-form-urlencoded MIME type, which is why the createTask() method consumes MediaType.APPLICATION_FORM_URLENCODED. An example of this call is located in Listing 10.

Listing 10. The jQuery.post function
$.post(url, [data], [callback], [type]);

The two calls are used in the Web page to call the RESTful Web services and display the results in the page.


The sample Web page

Create an index.html file inside the WebContent directory, as shown in Listing 11. The Web page includes the reference to the jQuery script as well as the functions to make the Ajax calls.

Listing 11. The complete index.html file
<!DOCTYPE html PUBLIC 
"-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html>
<head>
<title>Insert title here</title>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
<script type="text/javascript" src="scripts/jquery-1.3.2.min.js"></script>
<script type="text/javascript">
    //<![CDATA[
    var url = 'http://172.27.35.137:8080/Tasks/rest/tasks';

    function getTasks() {
        $.getJSON(url, function(data) {
            $.each(data, function(i, item) {
                $('#taskList').append(
                        '<p><a href="#" onclick="getTask(\'' + item.id
                                + '\');">' + item.name + '</a></p>');
            });
        });
    }

    function getTask(id) {
        $.getJSON(url + '/' + id, function(data) {
            $('#singleTask').html(
                    '<p>' + data.notes + ' (Due ' + data.dueDate + ') - '
                            + (data.complete ? 'Complete' : 'Incomplete')
                            + '</p>');
        });
    }

    function addTask() {

        var taskId = document.getElementById('taskId').value;
        var taskName = document.getElementById('name').value;
        var taskNotes = document.getElementById('notes').value;
        var taskDue = document.getElementById('due').value;
        var taskComplete = document.getElementById('complete').checked;

        $.post(url, {
            name : taskName,
            id : taskId,
            dueDate : taskDue,
            notes : taskNotes,
            complete : taskComplete
        });

        $('#taskList').html('');
        getTasks();

    }

    $(document).ready(function() {
        getTasks();
    });

    //]]>
</script>

</head>
<body>
<h4>Tasks:</h4>
<div id="taskList"></div>
<h4>Task detail:</h4>
<div id="singleTask"></div>
<form>
<fieldset>
<p><label for="taskId">ID<br />
<input type="text" id="taskId" name="taskId" /></label></p>
<p><label for="name">Name<br />
<input type="text" id="name" name="name" /></label></p>
<p><label for="notes">Notes<br />
<input type="text" id="notes" name="notes" /></label></p>
<p><label for="due">Due<br />
<input type="text" id="due" name="due" /></label></p>
<p><label for="complete">Complete<br />
<input type="checkbox" id="complete" name="complete" /></label></p>
<p><a href="#" onclick=
    addTask();;
>Add Task</a></p>
</fieldset>
</form>
</body>
</html>

This index.html file contains a few JavaScript functions that get the data from the RESTful Web services and display each task as a link. When you click on the link, the JavaScript functions get the details about the task that you clicked on.

After adding the jQuery library and new index.html file, re-export the project as a WAR file by following the same steps as before. Redeploy the WAR file through the Administrative Console. After the WAR file is redeployed, you can access the sample Web page at the following URL: http://localhost:8080/Tasks.

The Web page (shown in Figure 7) lets you test the calls from Ajax to your new RESTful Web services implemented in Apache Wink. Click on the links to see the task details, and add new tasks by typing in information and clicking the Add Task link.

Figure 7. The rendered index.html Web page
A window showing the tasks go to the store and walk the dog as well as the task details. ID, name, notes, and complete are boxes in which you can enter text.

Summary

Apache Wink is a framework that offers you the ability to easily write RESTful Web services in the Java programming language that consume and produce many different MIME content types out of the box, including plain text, XML, and JSON.

Using simple class and method annotations in your Java class, you can build a RESTful Web service that you can expose to many different clients, including Ajax. Using Ajax in your Web pages allows you to build a more fluid user experience with page refreshes.


Download

DescriptionNameSize
Sample code for this articlecode-apache-wink-ajax.zip25KB

Resources

Learn

Get products and technologies

Discuss

Comments

developerWorks: Sign in

Required fields are indicated with an asterisk (*).


Need an IBM ID?
Forgot your IBM ID?


Forgot your password?
Change your password

By clicking Submit, you agree to the developerWorks terms of use.

 


The first time you sign into developerWorks, a profile is created for you. Information in your profile (your name, country/region, and company name) is displayed to the public and will accompany any content you post, unless you opt to hide your company name. You may update your IBM account at any time.

All information submitted is secure.

Choose your display name



The first time you sign in to developerWorks, a profile is created for you, so you need to choose a display name. Your display name accompanies the content you post on developerWorks.

Please choose a display name between 3-31 characters. Your display name must be unique in the developerWorks community and should not be your email address for privacy reasons.

Required fields are indicated with an asterisk (*).

(Must be between 3 – 31 characters.)

By clicking Submit, you agree to the developerWorks terms of use.

 


All information submitted is secure.

Dig deeper into Web development on developerWorks


static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=1
Zone=Web development, Java technology
ArticleID=468307
ArticleTitle=Build rich Java Web applications with Apache Wink and Ajax
publish-date=02162010