CHAPTER 13

Alternative Clients

In Chapter 9, you developed a RESTful web services facility for the Collab-Todo application. You're now able to go to your browser, type in the URL, and perform basic CRUD operations against the domain objects using XML or JSON. In this chapter, you're going to see how you can use Groovy to consume the web services.

The first part of this chapter will take a look at writing Groovy scripts to consume the web services from the command line. The second part of this chapter will build upon the first part to build a rich Groovy client, using a Swing application that leverages the web services.

Overview

In Chapter 9, you developed a RESTful web service to perform CRUD operations using XML or JSON. Figure 13-1 is an overview of the components you will be developing in this chapter.

image

Figure 13-1. Overview

Setup

In this chapter, you will use Groovy to create the Groovy command-line scripts and rich Groovy client. In Chapter 1, you installed Groovy. To complete this chapter, you need to download and install some additional libraries that aren't part of the Groovy installation. The libraries you need to complete this chapter are SwingXBuilder,1 SwingX,2 JGoodies,3 Forms, and Glazed Lists.4 After you download the libraries, you have three choices of where to install them:5 on your CLASSPATH environment variable, in <GROOVY_HOME>lib, or in <USER_HOME>groovylib.

SwingXBuilder is a Groovy builder used to construct Swing user interfaces. SwingXBuilder uses the SwingLabs (SwingX) components. The SwingXBuilder and SwingX components make it much easier to build the Swing application than it would be to construct it by hand. JGoodies FormLayout is a popular Swing layout manager that uses a grid approach; you'll use it to position components within the Swing application. Glazed Lists is an extremely powerful Swing table component that you'll use to display to-do item summary information.

Use the information in Table 13-1 to download and install the libraries.

Table 13-1. Additional Libraries

Library Download URL
SwingXBuilder http://docs.codehaus.org/download/attachments/80916/swingxbuilder-0.1.5.jar?version=1
SwingX https://swingx.dev.java.net/files/documents/2981/76227/swingx-0.9.1.zip6
JGoodies Forms https://glazedlists.dev.java.net/files/documents/1073/94614/glazedlists_java14.jar
Glazed Lists http://www.jgoodies.com/download/libraries/forms/forms-1_2_0.zip

__________

6. You will need to unzip the file and copy the JAR files in the dist and liboptional directories to the appropriate location for your particular setup.


Note During the installation of Groovy, if you selected the Install Additional Modules options, you'll only need to download the JGoodies Forms and Glazed Lists libraries. In addition, if you selected the File Associations and PATHEXT options, you will be able to run scripts by typing the name of the script. For example, you can execute MyScript.groovy by typing MyScripts at the command line.


You now have the core components required to build the command-line scripts and the rich Groovy client.

Command-Line Scripts

All throughout the book, you have been using Groovy to build the Collab-Todo application. You have been using the Groovy language to build domain objects, controllers, and services, but that isn't the only way to use Groovy, as you saw in Chapters 13.

Command-Line Overview

Wouldn't it be nice if you had the ability to run CRUD operations on your to-do items from the command line? In this section, you will create the scripts to do just that. Chapter 9 introduced four client applications (GetRestClient.groovy, PutRestClient.groovy, PostRestClient.groovy, and DeleteRestClient.groovy) to demonstrate accessing the REST ful web service. You will use these as a starting point and make some enhancements.

Reading To-Do Items

Let's create a script that will invoke the RESTful web service facility created in Chapter 9 and display the results in a simple text format. The GetRestClient.groovy script from Chapter 9 is a good starting point. The script needs to be enhanced in the following man ner: it should accept the user ID and password as arguments, it should create the appropriate authorization header, and it should format the output. The bold portions of Listing 13-1 illustrate the enhancements made to GetRestClient.groovy to create GetAllTodos.groovy, the enhanced RESTful web service client.

Listing 13-1. GetAllTodos.groovy

01 import groovy.util.XmlSlurper
02
03 if (args.size() < 2 )
04 {
05   //USAGE()
06   println """Usage: groovy GetAllTodos userid password"""
07   System.exit(1)
08 }
09
10 // Define some thing we will need.
11 def userid = args[0]
12 def password = args[1]
13 def url = "http://localhost:8080/collab-todo/rest/todo"
14 def slurper = new XmlSlurper()
15
16 println " Getting All Todos for ${userid}:"
17
18 def conn = new URL(url).openConnection()
19 conn.requestMethod = "GET"
20 conn.doOutput = true
21
22 if (userid && password) {
23     conn.setRequestProperty("Authorization", "Basic ${userid}:${password}")
24 }
25
26 if (conn.responseCode == conn.HTTP_OK) {
27   def response
28
29   conn.inputStream.withStream {
30     response = slurper.parse(it)
31  }
32   println " No. of Todo Records: ${response.todo.size()}"
33   response.todo.each {
34     println "-------------------------------------"
35     println "Id:             ${it.@id}"
36     println "Name:           $it.name"
37     println "Note:           $it.note"
38     println "Owner:          ${it.owner.@id}"
39     println "Create Date:    $it.createDate"
40     println "Completed Date: $it.completedDate"
41     println "Due Date:       $it.dueDate"
42     println "Priority:       $it.priority"
43     println "Status:         $it.status"
44   }
45 }
46
47 conn.disconnect()

Lines 3–8 verify that the script is invoked with two arguments and displays a usage message if two arguments are not provided. Lines 11 and 12 assign the arguments to two local variables: userid and password. Lines 22–24 set up the connection/requests authorization header using userid and password. Line 26 accesses the web service and checks that it gave a valid response. If the response is valid, the script moves to lines 29–31, which read the XML response into a local variable using the XmlSlurper. Lines 32–44 iterate through each Todo item and display the item's content.


Note You can learn more about the XmlSlurper at http://groovy.codehaus.org/Reading+XML+using+Groovy%27s+XmlSlurper, or check out the Groovy API documents in the html directory under GROOVY_HOME.



Run the script by typing this command from a command line:

> groovy GetAllTodos user1 password

Assuming you did everything correctly, the output should look similar to this:


Getting All Todos:


No. of Todo Records: 2
-------------------------------------
Id:             3
Name:           User1 Todo 1
Note:           User1 Todo 1 Note
Owner:          2
Create Date:    2008-01-13 11:06:00.0
Completed Date: 2008-01-13 11:06:00.0
Due Date:       2008-01-13 11:06:00.0
Priority:       1
Status:         1
-------------------------------------
Id:             4
Name:           User1 Todo 2
Note:           User 1 Todo 2 Note
Owner:          2
Create Date:    2008-01-13 11:06:00.0
Completed Date: 2008-01-13 11:06:00.0
Due Date:       2008-01-13 11:06:00.0
Priority:       2
Status:         1

__________

This technique contains two security issues:

  • The users type in their user ID and password at the command line, so they may be exposed in a process list: A better approach is to prompt the script for the information. The upcoming Listing 13-2 uses this approach.
  • The request is transmitted to the web service in clear text: Any person who might be sniffing the web service port would see the authorization header that contains the user ID and password. A solution to this issue is to run the service under SSL (HTTPS). See Chapter 12 for more information on deploying and running with HTTPS.

Creating To-Do Items

Now that you know how to read to-do items, it's time to move on to creating new items. The process is the reverse of reading. You will make some enhancements to the PutRestClient.groovy script from Chapter 9, then you will prompt for the user ID, password, and to-do information. The bold portions of Listing 13-2 illustrate the enhancements made to PutRestClient.groovy to create CreateTodo.groovy, the enhanced RESTful web service client.

Listing 13-2. CreateTodo.groovy

01 import jline.ConsoleReader
02 import groovy.util.XmlSlurper
03
04 ConsoleReader cr = new jline.ConsoleReader()
05
06 // Prompt for UserID and Password
07 print "User id : "
08 def userid = cr.readLine();
09 print "Password: "
10 def password = cr.readLine(new Character('*' as char));
11
12 def url = "http://localhost:8080/collab-todo/rest/todo"
13 def userInfoUrl = "http://localhost:8080/collab-todo/userInfo?rest=rest"
14 def slurper = new XmlSlurper()
15
16 // Get the User Info
17 // It will be used later
18 def user_id
19 def user_firstName
20 def user_lastName
21 def conn = new URL(userInfoUrl).openConnection()
22 conn.requestMethod = "GET"
23 conn.doOutput = true
24
25 if (userid && password) {
26     conn.setRequestProperty("Authorization", "Basic ${userid}:${password}")
27 }
28
29 if (conn.responseCode == conn.HTTP_OK) {
30     def response
31
32     conn.inputStream.withStream {
33         response = slurper.parse(it)
34         user_id = response.@id
35         user_firstName = response.firstName
36         user_lastName = response.lastName
37     }
38 }
39
40 // Create the to-do
41 conn = new URL(url).openConnection()
42 conn.requestMethod = "PUT"
43 conn.doOutput = true
44 conn.doInput = true
45
46 if (userid && password) {
47     conn.setRequestProperty("Authorization", "Basic ${userid}:${password}")
48 }
49
50 // Values for CreatedDate
51 Calendar createDate = Calendar.getInstance();
52 def cdYear = createDate.get(Calendar.YEAR)
53 def cdMonth = createDate.get(Calendar.MONTH) + 1
54 def cdDay = createDate.get(Calendar.DAY_OF_MONTH)
55 def cdHour = createDate.get(Calendar.HOUR_OF_DAY)
56 def cdMin = createDate.get(Calendar.MINUTE)
57
58 // Prompt for Todo Information
59 println ""
60 print "Name:     "
61 def name = cr.readLine();
62 print "Priority: "
63 def priority = cr.readLine();
64 print "Status:        "
65 def status = cr.readLine();
66 print "Note:     "
67 def note = cr.readLine();
68
69 def data = "name=${name}&note=${note}&owner.id=${user_id}
70 &priority=${priority}&status=${status}&createDate=struct
71 &createDate_hour=${cdHour}&createDate_month=${cdMonth}
72 &createDate_minute=${cdMin}&createDate_year=${cdYear}
73 &createDate_day=${cdDay}"
74
75 conn.outputStream.withWriter {out ->
76     out.write(data)
77     out.flush()
78 }
79
80 if (conn.responseCode == conn.HTTP_OK) {
81     input = conn.inputStream
82     input.eachLine {
83         println it
84     }
85 }
86 conn.disconnect()

Line 4 creates a JLine ConsoleReader. JLine9 is a nice little utility that is included in the full Groovy install. You use ConsoleReader to read information that the user inputs. Lines 6–10 prompt for and read userid and password. Take a close look at line 10 and notice '*'; this is the echo character for the user input. Changing the echo character prevents the user's password from being displayed on the console.

Lines 21–38 call the user information web service to retrieve the user information that you'll use to create a new to-do item. The user information web service is a simple web service that we created to facilitate this process. For the sake of brevity, we won't go into the details of the web service; it is included in the code samples for this chapter in the Source Code/Download area of the Apress web site (http://www.apress.com).

Lines 50–56 create a Calendar object so that you can set the created date to the current date and time. Lines 58–67 prompt for the to-do information. Lines 69–73 use string interpolation to insert the values entered by the user into the query string that is sent to the web service on line 76.

Run the script by typing groovy CreateTodo from a command line. You will be prompted for a user ID and password. Next, fill in the to-do item name, the priority, the status, and a note. You can verify that the request to create to-dos succeeded by rerunning the GetAllTodos script.

Deleting To-Do Items

If you can create to-do items, then it only makes sense that you should be able to delete them as well. Once again, you will leverage the work you did in Chapter 9, with one minor enhancement. You will prompt for the user ID, password, and to-do item ID to be deleted. The bold portions of Listing 13-3 illustrate the enhancements made to DeleteRestClient.groovy to create DeleteTodo.groovy, the enhanced RESTful web service client.

Listing 13-3. DeleteTodo.groovy

01 import jline.ConsoleReader
02
03 ConsoleReader cr = new jline.ConsoleReader()
04
05 // Prompt for UserID and Password
06 def userid = cr.readLine("User id : ")
07 def password = cr.readLine("Password: ", new Character('*'as char))
08 println ""
09 def todo_id = cr.readLine("Todo Item id: ")
10
11 def url = "http://localhost:8080/collab-todo/rest/todo/${todo_id}"
12 def conn = new URL(url).openConnection()
13 conn.requestMethod = "DELETE"
14 conn .doOutput = true
15
16 if (userid && password) {
17     conn.setRequestProperty("Authorization", "Basic ${userid}:${password}")
18 }
19
20 if (conn.responseCode == conn.HTTP_OK) {
21     input = conn.inputStream
22     input.eachLine {
23       println it
24     }
25 }
26
27 conn.disconnect()

__________

Lines 5–9 prompt for the user ID, password, and to-do item ID to be deleted. Line 11 adds the to-do item ID to the URL. The only other part of this script that wasn't in the Chapter 9 version is the addition of the authorization header.

Run the script by typing groovy DeleteTodo from a command line. You will be prompted for the user ID, password, and to-do item ID to be deleted. You can verify that the to-do item was deleted by rerunning the GetAllTodos script.

Updating To-Do Items

Last, but not least, let's update an existing to-do item. This script is a pretty significant update to the Chapter 9 version, PostRestClient.groovy. The enhancements include prompting for the user ID, password, and to-do item ID to be updated; retrieving the current values for the to-do item to be updated; and prompting the user for the changes. The bold portions of Listing 13-4 illustrate the enhancements made to PostRestClient.groovy to create UpdateTodo.groovy, the enhanced RESTful web service client.

Listing 13-4. UpdateTodo.groovy

01 import jline.ConsoleReader
02
03 ConsoleReader cr = new jline.ConsoleReader()
04
05 // Prompt for UserID and Password
06 def userid = cr.readLine("User id : ")
07 def password = cr.readLine("Password: ", new Character('*' as char))
08 println ""
09 def todo_id = cr.readLine("Todo Item id: ")
10
11 // Things that can be updated
12 def id
13 def name
14 def priority
15 def status
16
17 // Get the current values
18 def url = "http://localhost:8080/collab-todo/rest/todo/${todo_id}"
19 def slurper = new XmlSlurper()
20
21 def conn = new URL(url).openConnection()
22 conn.requestMethod = "GET"
23 conn.doOutput = true
24 conn.setRequestProperty("Authorization", "Basic ${userid}:${password}")
25
26 if (conn.responseCode == conn.HTTP_OK) {
27   def response
28
29   conn.inputStream.withStream {
30     response = slurper.parse(it)
31   }
32   response.each {
33       id = it.@id
34       name = it.name
35       priority = it.priority
36       status = it.status
37  }
38 }
39
40 conn.disconnect()
41
42 // Prompt for Changes with current values
43 def tname = cr.readLine("Name (${name}): ")
44 name = tname ? tname : name
45 def tstatus = cr.readLine("Status (${status}): ")
46 status = tstatus ? tstatus : status
47 def tpriority = cr.readLine("Priority (${priority}): ")
48 priority = tpriority ? tpriority : priority
49
50 // Update the Todo
51 url = "http://localhost:8080/collab-todo/rest/todo"
52 conn = new URL(url).openConnection()
53 requestMethod = "POST"
54 conn.doOutput = true
55 conn.doInput = true
56
57 def data = "id=${id}&name=${name}&status=${status}&priority=${priority}"
58
59 conn.outputStream.withWriter { out ->
60   out.write(data)
61   out.flush()
62 }
63
64 if (conn.responseCode == conn.HTTP_OK) {
65   input = conn.inputStream
66   input.eachLine {println it }
67 }
68 conn.disconnect()

Lines 1–40 should look very similar to GetAllTodos.groovy. Lines 32–37 process the XML that is returned. In this case, the XML is the current to-do. The XML values are saved to local variables for use by lines 42–48, which prompt the user for changes to the to-do information. The prompt contains the current value. If the user presses Enter without making a change, the script will use the current value. The last point of interest is line 57, where the values are added to the query string.

To run the script, type groovy UpdateTodo from a command line. You will be prompted for the user ID, password, and to-do item ID to be updated. Next, you will be given the opportunity update the to-do's name, status, and priority. The current value of each field is displayed in the prompt. If you would like to keep the current value, just press Enter, and the current value will be retained. You can rerun the GetAllTodos script to verify that your updated was processed.

Command-Line Script Summary

This section illustrated the usage of command-line scripts to interact with the web service created in Chapter 9. You took the sample client scripts from Chapter 9 and enhanced them to be more usable. The enhancements included prompting for the user ID and password, creating an authorization header, and formatting the output.

The result is a simple, fast, command-line script to create, read, update, and delete to-do items in the Collab-Todo application. In the next section, you will take what you learned and create a rich Groovy client using the Groovy Swing facilities.

Rich Groovy Client

Creating a rich client with Groovy and Swing is a very wide and deep topic. There is no way we can cover it in one chapter. The goal in this section is to give you a small sample of the types of things possible using Groovy, SwingXBuilder, and a couple of popular open source Java libraries (SwingX, Glazed Lists, and JGoodies Forms). You will create a simple Collab-Todo application that leverages the RESTful web services.

Overview

The application will allow usersto log in, display their to-do items, add new to-do items, update to-do items, and delete to-do items. When the application is complete, it will look like Figure 13-2.

image

Figure 13-2. Collab-Todo application


Note The application leverages open source libraries; we will not be going into a detailed discussion about the code and the proper usage of the libraries. You can find tutorials and documentation about the libraries on their respective web sites. The complete source for the application is included in the Source Code/Download area of the Apress web site (http://www.apress.com).


Options, Alternatives, and Considerations

You'll need to make a lot of decisions when building a client application. These are just a few of the core questions you'll need to answer:

  • What presentation technology should you use?
  • What presentation components and frameworks should you use?
  • How should the application code be structured?

Choosing the Appropriate Presentation Technology

Groovy can do anything Java can do. If you think about the goal of developing the user interface from a Java perspective, you have two options: Swing or Standard Widget Toolkit (SWT). Either choice would work. You could just start coding Groovy and use Swing or SWT the same way you would in a normal Java program. However, if you've ever coded Swing or SWT, you probably aren't too excited right now.

Groovy uses builders10 to make the job easier. It has both the Swing family of builders (SwingBuilder11SwingXBuilder, and JideBuilder12) and the SWT builder (SwtBuilder13). Using Groovy and a builder will make the job of creating a client application much easier.

In general, Java developers are more likely to be familiar with Swing than SWT. Therefore, we'll show you how to use Swing as the presentation technology and SwingXBuilder to make it easier. SwingXBuilder extends SwingBuilder and provides access to all of the power of SwingBuilder, plus the Swing components from the folks at SwingLabs.14

Choosing the Presentation Components and Frameworks

Take a look at Figure 13-2. It shows a frame with a menu bar, toolbar, status bar, sortable table, labels, and text fields. When you use the system, you'll also see a login dialog and a tips dialog. You could code all of this by hand using plain Swing, but you don't need to. Instead, you can leverage some open source components and frameworks to make your job easier.

__________

10. Refer to Chapter 3 for a review of builders.

SwingXBuilder and the SwingLabs components are a good choice for the login dialog, tips dialog, and status bar. The Glazed Lists table component can help you make a great sortable table, and JGoodies Forms helps you arrange the components on the frame.


Note Check out both JideBuilder and JIDE Common Layer,15 which has some really good UI components. In addition, if you're into 2-D graphics, take a look at GraphicsBuilder16.JideBuilder and GraphicsBuilder are both courtesy of Andres Almiray.17


Structuring the Application

There are as many opinions about the best way to structure an application as there are Java developers. Many times, the features and characteristics of a language lead to some structures working better than others. However, it's important to remember that all approaches have pros and cons. We really don't have time to investigate all of the alternatives, so let's stand on the shoulders of giants and follow in their footsteps by adopting the approach the SwingX team used to organize the SwingX version of Groovy Console.

If you investigate the SwingXBuilder code base, you will discover a SwingX implementation of the Groovy Console. It is a port/refactor of the Groovy Console from the SwingBuilder implementation to the SwingXBuilder implementation. You can find the SwingXBuilder Groovy Console source code in the demos/console directory of the SwingXBuilder source code. James Williams,18 the creator of SwingXBuilder, did a nice job organizing the SwingXBuilder Groovy Console code. Well-organized code makes writing an application much easier. We will use the same code organization for the Collab-Todo application.

Figure 13-3 provides a high-level overview of the Collab-Todo application's structure.

__________

image

Figure 13-3. Structure of the Collab-Todo application

While the Controller and the BasicContentPane are the most important modules, a brief overview of all the modules is in order.

Main

The Main module is the entry point into the application. It creates the Controller and invokes the Controller's run closure.

Controller

The Controller module is exactly what it sounds like. It is the "C" of MVC. It is responsible for initiating the construction of the view and defining the application's actions and commands. It contains the application logic.

Actions

The Actions module defines the actions used within the application. The actions are tied to closures located in the Controller.

Views

The Views module is responsible for some simple view definition information and initiating the construction of the BasicMenuBar, the BasicToolBar, the BasicContentPane, and the BasicStatusBar. It represents the "V" of MVC.

BasicMenuBar

The BasicMenuBar contains the menus and menu items, and it ties the menu items to the appropriate actions.

BasicToolBar

The BasicToolBar is a graphical representation of important or frequently used actions. In the case of the Collab-Todo application, the only entry on the toolbar is the login button.

BasicContentPane

The BasicContentPane is the major graphical component of the application. It contains the to-do summary table and the to-do details. It also contains buttons that allow the user to add, save, and delete to-dos.

BasicStatusBar

The BasicStatusBar is a message area at the bottom of the screen.

HTTP Utilities

The HTTP utilities are helper classes to assist with the web service interactions. The utilities are used to manage the model (the "M" part of MVC).

RestController

The RestController represents the web services from Chapter 9.

Builder Overview

A Swing user interface can be thought of as a composite or a tree of graphical components. If you have programmed with Swing, you're undoubtedly familiar with the never-ending pattern of adding a component to its parent. It can be a complex, verbose mess. Groovy tackles the mess using builders.

If you have spent any time with Groovy, you have probably seen or used MarkupBuilder to construct an HTML or XML document. Builders can be used equally as well to build a Swing UI.

The easiest way to gain appreciation for SwingXBuilder is to see an example. Listing 13-5 shows how to use SwingXBuilder to create a simple user interface.

Listing 13-5. Creating a Simple UI Using SwingXBuilder

package com.apress.bgg.ui

import groovy.swing.SwingXBuilder
import static javax.swing.WindowConstants.EXIT_ON_CLOSE
import javax.swing.*

class SimpleUI {

    static void main(args) {
        def simpleUI = new SimpleUI()
        simpleUI.run()
    }

    def swing
    def count = 0

    def run = {
          swing = new SwingXBuilder()
          swing.lookAndFeel('system')

          // create the actions
          swing.action(id: 'exitAction',
            name: 'Exit',
            closure: this.&exit,
            mnemonic: 'x',
            accelerator: 'F4',
            shortDescription: 'Exit SimpleUI'
          )
          swing.action(id: 'aboutAction',
            name: 'About',
            closure: this.&showAbout,
            mnemonic: 'A',
            accelerator: 'F1',
            shortDescription: 'Find out about SimpleUI'
          )
          swing.action(id: 'clickAction',
            name: 'Click',
            closure: this.&click,
            shortDescription: 'Increment the Click Count'
          )

          // Define the Frame
          swing.frame(id:'simpleUIFrame', title: 'SimpleUI',
                location: [100,100],
                defaultCloseOperation: EXIT_ON_CLOSE
            ) {
          // Define the Menubar
          menuBar {
              menu(text: 'File', mnemonic: 'F') {
                  menuItem(exitAction)
              }
              glue()
              menu(text: 'Help', mnemonic: 'H') {
                  menuItem(aboutAction)
              }
          }

          // Define some stuff
          button(id:'clickButton', text:"Click Me", action: clickAction)
          // INSERT MORE STUFF HERE
            }

          swing.simpleUIFrame.pack()
          swing.simpleUIFrame.show()
    }

    void click(event) {
      count++
      swing.clickButton.text = "Clicked ${count} time(s)."
    }
  void showAbout(event) {
      JOptionPane.showMessageDialog(swing.simpleUIFrame,
      '''This is the SimpleUI Application''')
    }

    void exit(event) {
      System.exit(0)
    }
}

Executing the SimpleUI application creates the user interface shown in Figure 13-4.

image

Figure 13-4. The SimpleUI application

The SimpleUI application creates a Swing user interface that features a menu bar that contains a File menu, a Help menu, and a button in the content area. The text on the button changes every time the user clicks it. When the program starts, it invokes the run closure to build the UI. The run closure sets up the builder, then uses the builder to create three actions that will be used within the UI. Then the closure uses the builder to create the frame. The frame contains a menu bar, which contains the File and Help menus. Each of the menus contains menu items that reference the previously created actions. The frame also contains a button labeled Click Me, and a reference to the clickAction action.

If you take a closer look at the actions, you will notice that a parameter named closure was passed to the builder when creating the actions. In the case of clickAction, the closure to be executed is click. The click closure increments a counter and sets the button's text.

Now that you have a basic feel for using SwingXBuilder to create a user interface, we will return to the Collab-Todo application. We will focus on the Controller module, creating the view, and the HTTP utilities.

Creating the Main Module

All applications have a starting point, and Groovy Swing applications are no exception. Listing 13-6 illustrates the Collab-Todo application's startup.

Listing 13-6. The Main Routine

package com.apress.bgg.ui

import org.codehaus.groovy.runtime.StackTraceUtils
import com.apress.bgg.ui.Controller

class Main {
    static void main(args) {

        . . .

        def controller = new Controller()
        controller.run()
    }
}

As you can see, Controller performs the real startup and initialization. Main instantiates the Controller and invokes the run closure.

Creating the Controller Module

Just as in a web application, the Controller is the heart and mind of the application and creates the actions and views. It contains closures that are invoked by the actions. Listing 13-7 is a high-level view of the Controller.

Listing 13-7. High-Level View of the Controller

package com.apress.bgg.ui

import groovy.swing.SwingXBuilder
import groovy.util.slurpersupport.GPathResult

import java.awt.Component
import java.awt.Cursor
import javax.swing.*
import java.util.prefs.Preferences

import org.jdesktop.swingx.JXLoginPane
import org.jdesktop.swingx.JXTipOfTheDay
import org.jdesktop.swingx.tips.TipOfTheDayModel
import org.jdesktop.swingx.tips.TipLoader
import ca.odell.glazedlists.*
import ca.odell.glazedlists.gui.*
import ca.odell.glazedlists.swing.*

import com.apress.bgg.services.CTLoginService

import com.apress.bgg.http.utils.Get
import com.apress.bgg.http.utils.Delete
import com.apress.bgg.http.utils.Post
import com.apress.bgg.http.utils.Put

class Controller {
  . . .
  def run = {
      todoEventList = new BasicEventList()

      swing = new SwingXBuilder()

      // adjust the look and feel aspects.
      swing.lookAndFeel('system')

      // add controller to the SwingBuilder bindings
      swing.controller = controller

      // create the actions
      swing.build(Actions)

      // create the view
      swing.build(Views)

      swing.consoleFrame.pack()
      swing.consoleFrame.show()
  }

  void exit(event) { System.exit(0) }

  void doTips() { . . . }
  void showTips(event) { . . . }
  void showAbout(event) { . . . }
  void showLogin(event) { . . .
  void fullStackTraces(EventObject evt) { . . .}
  void showToolbar(EventObject evt) { . . . }
  def status = { message -> swing.status.text = "$message" }

  void loadData(){ . . . }
  void deleteTodo(event) { . . .}
  void saveTodo(event) { . . . }
  void addTodo(event) { . . . }
}

When the application starts, the Main module instantiates the Controller and invokes the Controller's run closure. The run closure uses SwingXBuilder to construct the UI and then puts it on the screen. Everything else in the Controller is application logic that is tied to actions.

The nonbold closures are standard code that you would expect to see for handling the login dialog, displaying tips, displaying the About dialog, and handling other miscellaneous actions. The loadData, deleteTodo, and saveTodo closures interact with the web service using the HTTP utilities, which we'll cover shortly.

When the user logs in, the application invokes loadData to retrieve the user's to-do items. Listing 13-8 illustrates using the HTTP utility Get to retrieve the user's information.

Listing 13-8. Loading Data from the RESTful Web Service

void loadData(){
  status "Loading Data"

  def get = new Get(url: APP_URL,
      userName: loginService.name,
      password: new String(loginService.password))

   def todos = new XmlSlurper().parseText( get.text )

  todoEventList.clear()
  todoEventList.addAll( todos.todo.list() )

  status "Finished Loading Data"
}

The closure puts a message on the status bar, creates the Get HTTP utility to interact with the web services, and parses the resulting XML with XMLSlurper. The results are then added to the todo collection, which notifies the application of new data using change events so that the screen can be updated. Finally, the status message is updated.

The user can delete to-do items by selecting the to-do from the summary table and then selecting the Delete button. The Delete button invokes deleteAction, which runs deleteTodo. Listing 13-9 illustrates the implementation of the delete logic.

Listing 13-9. Deleting a To-Do

void deleteTodo(event) {
   def delete = new Delete(url: APP_URL+"/${selectedTodo().@id}",
       userName: loginService.name,
       password: new String(loginService.password))
   delete.text
   loadData()
}

The deleteTodo closure creates a Delete HTTP utility to interact with the web services. It then invokes the web services and repopulates the user's information. If you recall, when using web services to delete information, the ID of the item to be deleted is appended to the URL, and the request method is set to DELETE. The bold code shown in Listing 13-9 retrieves the ID of the currently selected to-do using a helper method.

The user can add a new to-do item by clicking the Add button. This adds a blank to-do to the summary table. The user selects the blank to-do from the summary table and then enters the to-do details. Next, the user clicks the Save button to save the new to-do. The Save button invokes saveAction, which runs saveTodo. Listing 13-10 illustrates the implementation of the save logic.

Listing 13-10. Saving a To-Do

void saveTodo(event) {
     selectedTodo().name = swing.nameTextField?.text
     selectedTodo().priority = swing.priorityTextField?.text
     selectedTodo().status = swing.statusTextField?.text
     selectedTodo().completedDate = swing.completedTextField?.text
     selectedTodo().createDate = swing.createTextField?.text
     selectedTodo().dueDate = swing.dueTextField?.text
     selectedTodo().note = swing.noteTextField?.text

      // Save or Update?
      // if don't have an ID, then save else update
      if (selectedTodo().@id) {
            def put = new Put(url: APP_URL,
               userName: loginService.name,
               password: new String(loginService.password))
            put.queryString.add("name", selectedTodo().name)
            put.queryString.add("priority", selectedTodo().priority)
            put.queryString.add("status", selectedTodo().status)
            put.queryString.add("note", selectedTodo().note)

            // Construct a create date
            // Values for createDate
            Calendar createDate = Calendar.getInstance();
            def cdYear = createDate.get(Calendar.YEAR)
            def cdMonth = createDate.get(Calendar.MONTH)+1
            def cdDay = createDate.get(Calendar.DAY_OF_MONTH)
            def cdHour = createDate.get(Calendar.HOUR_OF_DAY)
            def cdMin = createDate.get(Calendar.MINUTE)

            put.queryString.add("createDate",
                   "struct&createDate_hour=${cdHour}&createDate_month=${cdMonth}&
                    createDate_minute=${cdMin}&createDate_year=${cdYear}&
                    createDate_day=${cdDay}")
            put.content = content
            put.text
      } else {
            // Update
            . . . .
      }
      loadData()
}

The saveTodo closure saves the screen values of the to-do details to local variables, and it populates an XML string using the values. Then, the closure determines if the intent is to save a new to-do or update an existing one. If the currently selected to-do doesn't have an ID, then the closure saves it.

Next, you create a Put HTTP utility to interact with web service, populate the request with the XML string and a create date, and invoke the service. Finally, you use the loadData closure to repopulate the user's information.

In this section, you've learned about the more important portions of the Controller.You have seen how to use SwingXBuilder to construct the view components and how the Controller interacts with the web services. The next step is to look deeper into the view creation.

Creating the View

Most IT people like to think that UI programming is easy. The truth of the matter is that it isn't as easy as everyone thinks it is. Creating a good, well-organized UI can be tough work and require lots of code. However, the Swing and SwingX builders and some open source Swing component libraries make the job much easier.

The Controller creates SwingXBuilder, and it also creates actions and views by passing Actions and Views scripts to the builder. You accomplish this by using the builder's build closure. The build closure allows you to pass a script as a closure, and it allows the code to be divided into separate modules, resulting in a code base that is easier to manage.If you take a look at Actions.groovy and Views.groovy, located in the comapressggui directory, you will see that they are scripts. You saw how to create actions in the SimpleUI application, so now let's focus on creating the views. Listing 13-11 is a partial listing of Views script.

Listing 13-11. The Views Script

package com.apress.bgg.ui
. . .
frame(
    title: 'Collab Todo',
    location: [100,100],
    iconImage: imageIcon(Controller.ICON_PATH).image,
    defaultCloseOperation: DO_NOTHING_ON_CLOSE,
    id:'consoleFrame'
) {
    build(menuBarClass)
    build(toolBarClass)
    build(contentPaneClass)
    build(statusBarClass)

The script creates and configures a frame and then uses the builder's build closure to construct the menu bar, toolbar, content pane, and status bar. Now let's take a look at the content pane. The content pane is responsible for constructing the main portion of the user interface. The content pane is composed of two parts: a summary table and the details. Listing 13-12 is a partial listing of the script used to construct the content pane.

Listing 13-12. Constructing the Content Pane

01 package com.apress.bgg.ui.view
02 . . .
03 swing = controller.swing
04
05 def selectedIndex = 0
06
07 // Create Sorted List for use by the table
08 EventList todoEventList = controller.todoEventList
09 SortedList sortedTodos = new SortedList(todoEventList,
10         { a, b -> b.name.text() <=> a.name.text() } as Comparator)
11
12 /*
13 * Helper method to get the current model/row
14 */
15 def selectedTodo = {
16   selectedIndex = swing.table.selectedRow
17     if( selectedIndex != −1 ){
18         selectedIndex = sortedTodos?.getSourceIndex(selectedIndex)
19         return todoEventList[selectedIndex]
20     }
21 }
22 controller.selectedTodo = selectedTodo
23
24 /*
25 * Define a Summary Table
26 */
27 def columnNames = ["Name","Priority","Status","Note"]
28 def summaryTable = scrollPane(){
29     table( id: 'table', model:
30        new EventTableModel( sortedTodos, [
31           getColumnCount: { return columnNames.size() },
32           getColumnName: { index ->
33              columnNames[index]
34           },
35           getColumnValue: { object, index ->
36                object."${columnNames[index].toLowerCase()}".text()
37           }] as TableFormat))
38     def tableSorter = new TableComparatorChooser(swing.table,
39         sortedTodos, AbstractTableComparatorChooser.SINGLE_COLUMN )
40 }
41
42 splitPane(id: 'splitPane', resizeWeight: 0.50F,
43     orientation: VERTICAL_SPLIT)
44 {
45
46      // Set up the Summary Table form using JGoodies Forms
47      FormLayout layout = new FormLayout(
48             "3dlu, 200dlu, 3dlu, pref, 3dlu, pref,3dlu", // columns
49             "3dlu, p, 3dlu, p, 3dlu, p, 3dlu");      // rows
50      CellConstraints cc2 = new CellConstraints()
51
52      panel(layout: layout){
53          widget( constraints: cc2.xyw(2,2,6), id: "summaryTable" , summaryTable)
54          widget( constraints: cc2.xy(6,4), id: "addButton",
55                 new JButton(addAction))
56      }
57
58      // Set up the to-do details form using JGoodies Forms
59      FormLayout layout2 = new FormLayout(
60         "3dlu,right:pref, 3dlu, 90dlu, 3dlu, pref, 3dlu, pref, 3dlu", // columns
61         "3dlu, p, 3dlu, p, 3dlu, p, 3dlu, p, 3dlu, p, 3dlu, p, 3dlu, p, 3dlu,
62          p, 3dlu"); // rows
63      CellConstraints cc = new CellConstraints()
64
65      // Define the detail panel
66      // container( new FormDebugPanel(layout: layout2) ){
67      panel( layout: layout2 ){
68           label(  constraints: cc.xy(2,2), text: "Name:" )
69           widget( constraints: cc.xyw(4,2,6),
70              new JTextField(columns:20), id: "nameTextField" )
71           label(  constraints: cc.xy(2,4), text: "Priority:" )
72           widget( constraints: cc.xyw(4,4,6),
73              new JTextField(columns:20), id: "priorityTextField" )
74           label(  constraints: cc.xy(2,6), text: "Status:" )
75           widget( constraints: cc.xyw(4,6,6),
76              new JTextField(columns:20), id: "statusTextField" )
77           label(  constraints: cc.xy(2,8), text: "Completed Date:" )
78           widget( constraints: cc.xyw(4,8,6),
79              new JTextField(columns:20), id: "completedTextField" )
80           label(  constraints: cc.xy(2,10), text: "Create Date:" )
81           widget( constraints: cc.xyw(4,10,6),
82              new JTextField(columns:20), id: "createTextField" )
83           label(  constraints: cc.xy(2,12), text: "Due Date:" )
84           widget( constraints: cc.xyw(4,12,6),
85              new JTextField(columns:20), id: "dueTextField" )
86           label(  constraints: cc.xy(2,14), text: "Note:" )
87           widget( constraints: cc.xyw(4,14,6),
88              new JTextField(columns:40), id: "noteTextField" )
89           widget( constraints: cc2.xy(6,16), id: "saveButton",
90            new JButton(saveAction))
91           widget( constraints: cc2.xy(8,16), id: "deleteButton",
92            new JButton(deleteAction))
93      }
94
95      // Data Bind the fields (view) to the model.
96      // sourceValue = model
97      // target      = view
98      bind(source:swing.table.selectionModel, sourceEvent:'valueChanged',
99         sourceValue: { selectedTodo()?.name },
100        target: swing.nameTextField, targetProperty: 'text')
101     bind(source:swing.table.selectionModel, sourceEvent:'valueChanged',
102        sourceValue: { selectedTodo()?.priority },
103        target: swing.priorityTextField, targetProperty: 'text')
104     bind(source:swing.table.selectionModel, sourceEvent:'valueChanged',
105        sourceValue: { selectedTodo()?.status },
106        target: swing.statusTextField, targetProperty: 'text')
107     bind(source:swing.table.selectionModel, sourceEvent:'valueChanged',
108        sourceValue: { selectedTodo()?.completedDate },
109        target: swing.completedTextField, targetProperty: 'text')
110     bind(source:swing.table.selectionModel, sourceEvent:'valueChanged',
111        sourceValue: { selectedTodo()?.createDate },
112        target: swing.createTextField, targetProperty: 'text')
113     bind(source:swing.table.selectionModel, sourceEvent:'valueChanged',
114        sourceValue: { selectedTodo()?.dueDate },
115        target: swing.dueTextField, targetProperty: 'text')
116     bind(source:swing.table.selectionModel, sourceEvent:'valueChanged',
117        sourceValue: { selectedTodo()?.note },
118        target: swing.noteTextField, targetProperty: 'text')
119 }

The summary table is a sortable table that supports property change events. Creating this functionality from scratch would be error prone and require a lot of work. Instead, you can use the Glazed Lists table component to do the heavy lifting. Lines 7–10 get the list of to-dos from the Controller and wrap them with a sortable list. Lines 27–39 create the table inside of a scroll pane.

The next step is to lay out the top portion of the content pane and add the table and the Add button. There are many different ways to lay out the form. For this example, we're using the JGoodies FormLayout component, which uses a grid approach to position the components. Lines 47–50 define the grid. Line 52 creates a panel using the JGoodies FormLayout manager. Lines 53 and 54 add the table and the Add button. Using the JGoodies FormLayout manager requires using the builder's widget closure to add the table and the Add button at specified locations in the grid.

The to-do detail section of the content pane is a simple list of to-do details and the Save and Delete buttons. Lines 58–62 define the layout grid for the detail area. Line 67 creates a panel using the detail layout manager. Lines 68–92 add the to-do details to the panel as a label and a text field. As explained previously, using the JGoodies FormLayout manager requires using the builder's widget closure.

When the user selects a row from the summary table, the to-do item's details should be displayed in the details section. You could do this with an event listener, but a better, more contemporary, approach is to use the builder's data-binding abilities. Lines 98–118 define the data binding between the list of to-dos supporting the table and the to-do detail fields. Whenever the user selects a row in the table, the table's selection model changes and fires off a valueChanged event. Whenever there is a valueChanged event on the table's selection model, the data binding populates the to-do detail fields with the to-do's value and displays the results.

We have barely scratched the surface of what is possible using Groovy, builders, and open source component libraries. The important thing to remember is that "Groovy is Java," and that allows you to use any of the Java components and libraries you want to build an application. The next section will explore the HTTP utilities that are used to interact with the web service.

HTTP Utilities (Get, Put, Post, and Delete)

In the "Command-Line Scripts" section of this chapter, you learned how to create four to-do scripts that correspond to the four HTTP request methods. These scripts worked well, but they were specific to the task at hand—that is, interacting with the Collab-Todo web service to maintain to-do items.

The Collab-Todo rich client also needs to interact with the web service. You can use the same technique to create a couple of utility objects to perform the web service inter actions. Listing 13-13 illustrates the results of refactoring the GetAllTodos script into a Get utility class.

Listing 13-13. HTTP Get Utility

package com.apress.bgg.http.utils

class Get{
  String url
  QueryString queryString = new QueryString()
  String text
  def userName
  def password

  String getText(){
    def response
    def conn = new URL(toString()).openConnection()
    conn.requestMethod = "GET"
    conn.doOutput = true

    if (userName && password) {
        conn.setRequestProperty("Authorization", "Basic ${userName}:${password}")
    }

    if (conn.responseCode == conn.HTTP_OK) {
        response = conn.content.text
    } else {
        response =  "URL: " + this.toString() + " " +
          "RESPONSE CODE: " + responseCode
    }
    conn.disconnect()
    return response
  }

    String toString(){
    return url + "?" + queryString.toString()
  }
}

There isn't anything special about the implementation, which is very similar to the GetAllTodos script. It takes a URL, a username, a password, and, optionally, some query parameters. Listing 13-14 illustrates its usage.

Listing 13-14. Usage of the Get Utility

def get = new Get(url: "http://localhost:8080/collab-todo/rest/todo",
    userName: loginService.name,
    password: new String(loginService.password))
def todos = new XmlSlurper().parseText(get.getText())

We won't cover the details of the Put , Post, and Delete classes, but they follow the same approach and are included in the Source Code/Download area of the Apress web site (http://www.apress.com).

Summary

In this chapter, you created command-line scripts and a rich client written in Groovy to interact with the Collab-Todo web services. This required you to download and install the full Groovy installation, which provides access to additional Groovy modules and open source libraries.

The command-line scripts leveraged Chapter 9's web service client scripts, which you extended to give the user the ability to log in to the web services and view all of the to-dos, create a to-do, update a to-do, and delete a to-do.

Next, you turned your attention to creating a rich client application using SwingXBuilder, JGoodies FormLayout manager, and the Glazed Lists table component. You started by writing a simple application that counted button clicks. This simple application gave you a basic understanding of SwingXBuilder and the process of creating the actions, menus, and buttons. With this information, you were able to start constructing the user interface portion of the application.

You started building the application by creating a Controller that was responsible for building the view, executing the application logic, and interacting with the web services.The Controller delegated the construction of the view to the Views script, which used SwingXBuilder to build the menus, toolbar, content pane, and status bar. The content pane was the main focus. The content pane script created the summary table, the detail fields, and the Add, Delete, and Save buttons. You used the JGoodies FormLayout manager to position these items on the screen.

Finally, you interacted with the Collab-Todo web service. You took what you learned from creating the command-line scripts to create a couple of HTTP utility classes to perform the web service calls. You now have a rich client application to maintain your to-dos.

The purpose of this chapter was to help you see that Groovy and Grails aren't just web application frameworks. Our goal was to give you a sample of what is possible. You are only limited by your imagination.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset