Appendix. Porting a legacy application

You’ve seen how Griffon can be used to create desktop applications from scratch, and you’ve had a lot of fun while doing so. Sadly, all isn’t fun and giggles when it comes to dealing with existing Java-based applications, because you can’t go back in time and begin the project with Griffon as the starting tool. But as the saying goes, hope dies last, and there’s hope for solving this problem. No, it’s not a time machine, no matter how much we long for a TARDIS; rather, it’s a set of tips and features that can ease the transition from a full Java Swing application to a Griffon-enabled one.

Let’s begin with the visible aspect of an application.

Handling legacy views

You might recall that back in section 4.6, we discussed two options that allow you to adapt a legacy view into a Griffon application without changing the source of the existing view classes: the ability to wrap a Swing GUI Builder (formerly Matisse) view into a Groovy view script, or use the Abeille Forms Designer plugin if your views rely on AFD. Here’s a quick summary of both approaches.

Swing GUI Builder views

A Swing GUI Builder view class is normally generated via templates. The most usual template creates a subclass of JComponent, registers a field for each visible component, and finally arranges all components using a complex set of instructions using GroupLayout’s API. The Griffon script generate-view-script relies on these facts and generates a view script that exposes each of the visible components and returns the top-level container (the subclass of JComponent). This script expects the name of the class to be converted, whose file should reside somewhere in the application’s source directories. Where to put the file is up to you; we recommend src/main instead of griffon-app/views, because the class isn’t a real Griffon view.

Here’s how a wrapped view may look:

widget(new LoginDialog(), id:'loginDialog')
noparent {
  bean(loginDialog.usernameField, id:'usernameField')
  bean(loginDialog.passwordField, id:'passwordField')
  bean(loginDialog.okButton, id:'okButton')
  bean(loginDialog.cancelButton, id:'cancelButton')
}
return loginDialog

You can infer from this script that the top-level class LoginDialog is a subclass of JComponent. You can also infer that the four beans declared afterward are visible components that belong to said class. Notice the usage of the noparent node to avoid exposing the components to unsuspecting containers. Once all components are available in a view, you can apply them to the same techniques and tricks applicable for all other nodes, such as binding.

generate-view-script comes bundled with the default distribution; there’s no need to install additional software to make it work. But you do need to add to your application whatever dependencies the legacy view class requires, such as grouplayout.jar or swing-application.jar.

Abeille Forms Designer views

Unlike the previous option, you do need to install additional software in order to work with AFD views. Fortunately, that software comes bundled as a Griffon plugin, which means installing it is just a command invocation away:

$ griffon install-plugin abeilleforms-builder

AFD views are stored in either XML or a custom binary format. Regardless of which one is used, the plugin should be able to expose the contents of a form. Given that form definitions aren’t code, it’s better to place them under griffon-app/resources; otherwise, use your best judgment depending on your preferences. The only constraint is that the form definitions must be available in the application’s classpath.

Here’s how the previous view might look with AFD. We also added a few customizations for binding and registering actions on the buttons:

formPanel("login.xml", id: 'loginPanel')
noparent {
  bean(model, username: bind{ usernameField.text })
  bean(model, password: bind{ passwordField.text })
  bean(okButton, actionPerformed: controller.loginOk)
  bean(cancelButton, actionPerformed: controller.loginCancel)
}
return loginPanel

The code is simple. It follows the same approach as the Swing GUI Builder view wrapper: exposing the top-level form as a container plus all of its children components.

But what happens if your view can’t be wrapped with any of these options? No problem. Assuming the view class follows a design similar to the one used by Swing GUI Builder views, you can manually create a view script that exposes the top-level container and its children, using a combination of widget, container, and bean nodes. The last approach we’ll cover is a full-blown Java view, no Groovy at all.

Custom Java-based views

If for some reason you feel the need to build a view in 100% Java or you’re unable to use Groovy, don’t worry; you can still take advantage of Griffon. For the first case, you only need to write a class that provides the same behavior as a Griffon view, including the hooks to the application’s life cycle. If you’re thinking already about how to accomplish such a task, then don’t bother too much; it already exists, and here’s how you can take advantage of it.

First you’ll convert an existing Groovy-based view into a Java source file. Say, for example, that you have an existing application with an MVC group named sample. By convention, it will have a view script named SampleView located in the sample package in griffon-app/views. Armed with this knowledge, you can invoke the following command:

$ griffon replace-artifact sample.SampleView –type=view –fileType=java

Looking carefully at the command’s arguments, you can expect the source file griffonapp/views/sample/SampleView.groovy to be replaced by another file with the same name but with .java as its extension. Its contents are also different from the original file. This is how the view looks after it has been converted from Groovy to Java:

package sample;

import java.awt.*;
import javax.swing.*;
import java.util.Map;

import griffon.swing.SwingGriffonApplication;
import org.codehaus.griffon.runtime.core.AbstractGriffonView;

public class SampleView extends AbstractGriffonView {
    // these will be injected by Griffon
    private SampleController controller;
    private SampleModel model;

    public void setController(SampleController controller) {
        this.controller = controller;
    }

    public void setModel(SampleModel model) {
        this.model = model;
    }

    // build the UI
    private JComponent init() {
        JPanel panel = new JPanel(new BorderLayout());
         panel.add(new JLabel("Content Goes Here"), BorderLayout.CENTER);
        return panel;
    }

    @Override
    public void mvcGroupInit(final Map<String, Object> args) {
        execSync(new Runnable() {
            public void run() {
                Container container = (Container)
  getApp().createApplicationContainer();
                if(container instanceof Window) {
                   containerPreInit((Window) container);
                }
                container.add(init());
                if(container instanceof Window) {
                   containerPostInit((Window) container);
                }
             }
        });
     }

     private void containerPreInit(Window window) {
         if(window instanceof Frame) ((Frame) window).setTitle("sample");
         window.setIconImage(getImage("/griffon-icon-48x48.png"));
         // uncomment the following lines if targeting +JDK6
         // window.setIconImages(java.util.Arrays.asList(
         //      getImage("/griffon-icon-48x48.png"),
         //      getImage("/griffon-icon-32x32.png"),
         //      getImage("/griffon-icon-16x16.png")
         // ));
         window.setLocationByPlatform(true);
         window.setPreferredSize(new Dimension(320, 240));
     }

     private void containerPostInit(Window window) {
         window.pack();
         ((SwingGriffonApplication)
     getApp()).getWindowManager().attach(window);
     }

     private Image getImage(String path) {
         return Toolkit.getDefaultToolkit().getImage
         (SampleView.class.getResource(path));
     }
}

A quick survey of the code reveals that the new view extends from a particular class (AbstractGriffonView), which you can assume implements all the required behavior of a Griffon view. There are also properties that match the other two MVC members of the group, the controller and the model. You can delete those if they aren’t needed. Next you find the hook into the application’s life cycle. Recall from chapter 6 that the mvcGroupInit method is called by the application once all members of a group have been instantiated but before the building of the group has been completed. This is the perfect time to customize the view. Also of important note is the usage of the execInsideUISync method to guarantee that the UI components are built inside the UI thread. This method is one of the threading options we discussed back in chapter 7. The default template suggests that you build the UI using a private method named init; this is a standard practice with generated code. The key aspect is that you’re free to implement this method as you see fit.

XML-based views

There’s one last alternative to building an UI declaratively: using XML. For some strange reason, XML has been the preferred go-to format for externalizing almost every aspect of a Java application since the early days. We won’t engage in a discussion of whether XML is good or bad, but the moment you step outside of declarative programming by adding behavior, well ... let’s just say there are better ways to spend your time than battling the angle-bracket monster.

Java-based Griffon views come with a ready-to-use mechanism for dealing with XML. You may have noticed that the AbstractGriffonView class exposes a pair of methods named buildViewFromXml: one takes a map as a single argument, and the other takes a map and a string. Both methods will read an XML definition, but the first relies on the convention over configuration precept to determine the name of the file while the other takes the name as an argument. What could be the convention here? If you guessed the name of the view class, then you’re on the correct path again.

Suppose you have a Java-based view whose full qualified class name is com.acme.SampleView. By convention, the XML file should be named SampleView.xml, and it should be somewhere in the classpath under a directory structure equal to com/acme. XML-based views are typically placed in griffon-app/resources, but this isn’t a strict requirement. The code needed to read and bootstrap a simple externalized view looks like the following snippet:

package com.acme;

import java.util.Map;
import org.codehaus.griffon.runtime.core.AbstractGriffonView;

public class SampleView extends AbstractGriffonView {
    private SampleController controller;
    private SampleModel model;

    public void setController(SampleController controller) {
        this.controller = controller;
    }

    public void setModel(SampleModel model) {
        this.model = model;
    }

    public void mvcGroupInit(Map<String, Object> args)
        buildViewFromXml(args);
    }
}

Pay attention to the bolded section—that’s all you need to instruct the view to read the default XML file. The args parameter contains all the elements the view’s builder might require, such as the controller and model. The XML file could look like this:

<application title="app.config.application.title"
             pack="true">
    <actions>
        <action id="'clickAction'"
                name="'Click'"
                closure="{controller.click(it)}"/>
    </actions>

    <gridLayout cols="1" rows="3"/>
    <textField id="'input'" columns="20"
        text="bind('value', target: model)"/>    <textField id="'output'"
  columns="20"
        text="bind{model.value}" editable="false"/>
    <button action="clickAction"/>
</application>

It resembles a Groovy view, doesn’t it? That’s because this feature directly translates the XML text into a Groovy representation that the view’s builder can understand. As a matter of fact, it generates an inline script that is equivalent to the following:

application(title: app.config.application.title, pack: true) {
  actions {
    action(id: 'clickAction', name: 'Click', closure: {controller.click(it)})
  }
  gridLayout(cols: 1, rows: 3)
  textField(id: 'input', text: bind('value', target: model), columns: 20)
  textField(id: 'output', text: bind{model.value}, columns: 20, editable:
  false)
  button(action: clickAction)
}

Notice the usage of Groovy expressions in the XML. It’s like having a ready-made expression language! Also, be sure to use escape literal values with single or double quotes, as is done here for the id properties of each text field; otherwise the builder will complain. You might be thinking, “If the XML looks so close to what you would write in Groovy, why bother with the XML in the first place?” We wonder that too; alas, some people still prefer XML over a scripting language. It’s just the way the world works.

Full Java MVC members

As you just saw in the previous section, the ability to change the source type of a view is useful and isn’t restricted to views; you can replace a controller, a model, or even a service in the same manner. You can also create a new group using Java as the source type from the start—just make sure you specify the fileType parameter, as demonstrated in the following example:

$ griffon create-mvc custom -fileType=java

This command will generate all MVC members using a Java-based template instead of the default Groovy one. You’ve seen the view already, so let’s take a peek at the controller and model. First comes the controller:

package sample;

import java.awt.event.ActionEvent;
import org.codehaus.griffon.runtime.core.AbstractGriffonController;

public class CustomController extends AbstractGriffonController {
  private CustomModel model;
  public void setModel(CustomModel model) { this.model = model; }
  public void action(ActionEvent e) {}
}

This class must comply with the contract of a Griffon controller—that’s why it extends from a specific class (AbstractGriffonController). The template shows how actions can be defined. Because you’re working with Java now, using closures is out of the question, but actions can be defined as public methods. You can change the type of the parameter; ActionEvent is the most usual case, which is why it’s suggested by the template.

Next is the model:

package sample;

import org.codehaus.griffon.runtime.core.AbstractGriffonModel;

public class CustomModel extends AbstractGriffonModel {
  private String input;

  public String getInput() { return input; }
  public void setInput(String input) {
    firePropertyChange("input", this.input, this.input = input);
  }
}

As with the previous two artifacts, the model requires a custom superclass (Abstract-GriffonModel). The template shows how a simple observable property can be defined. It must be done in this way because @Bindable can’t be used with Java code.

It’s worth mentioning that there’s a specific interface for each artifact type provided by Griffon. Following the naming conventions, their class names are as follows:

griffon.core.GriffonModel
griffon.core.GriffonView
griffon.core.GriffonController
griffon.core.GriffonService

You’re required to implement the corresponding interface for a particular artifact. Given that the behavior is similar for all artifacts, Griffon provides base implementations for each interface; these are the abstract classes you saw in the previous code snippets. But nothing prevents you from implementing any of these interfaces from scratch.

If you’re wondering whether a different source file type can be applied to the initial MVC group without resorting to calling replace-artifact, then the answer is yes. Just be sure to specify the fileType argument when issuing the call to create-app.

Preferring services over controllers

You know now that every MVC member can be written in Java too. Although that ability can be useful, sometimes it’s better to keep the encapsulation of legacy code and not mix it with specific Griffon artifact behavior. This is where services come to your aid. You can use a service as a bridge between controllers and the target legacy code. In this way, you can take full advantage of Groovy in your controller, models, and views while still accessing all the behavior from the legacy classes from a safe vantage point.

Keep in mind that services are treated as singletons and are automatically instantiated by the framework. Services are also automatically registered as application event listeners, which can help at times to initialize any components belonging to the legacy code.

Using events to your advantage

Recall from chapters 2 and 8 that Griffon provides a powerful event mechanism that’s paired with the application’s life cycle. This enables you to register initialization code for any legacy component that can be performed at the right time. Be aware that addons fire a new set of events when they’re being processed by the application while it’s starting up; you’ll have more options to micromanage the instantiation and customization of legacy code.

Finally, remember that any component can be transformed into an event publisher if it’s annotated with @EventPublisher or, in the case of Java code, if it relies on Event-Router to send out notifications. Events make it simple to communicate disparate components, which is always a good thing to have in mind when you’re dealing with legacy code.

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

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