January 17, 2012

How to implement MVC (Model View Controller) Pattern in Swing

The MVC pattern is the most common pattern when it comes to web framework, but when building a rich client with swing or by using a rich client framework such as, Eclipse Rich Client Platform, Eclipse RCP , or Spring Rich Client Project, Spring RCP or trying to develop a similar platform yourself, thing tends to fast get very complicated. Why is that? Well, first Swing is very low tech. To do even the most simple thing requires quite a lot of lines of code. Another reason is that a lot of people have not taken their time to fully understand the MVC pattern. And maybe last there are a lot of misunderstanding what a view is in the MVC pattern and the Swing model. The Swing model is entire a presentation necessity and therefore only belong to the view.

So hopefully what I will achieve in this blog is to explain the MVC pattern and how to implement it with Swing. What I will not do in this blog is to explain how to use certain Swing components, their are quite a few tutorials out there, e.g. Oracle own Swing Tutorial, http://docs.oracle.com/javase/tutorial/uiswing/.

Last start with explaining the MVC pattern fundamental. Maybe the easiest way to explain MVC is to look at how things work in the web world.


What happens:
  1. The View builds the graphical interface and reacts to user interaction, by redirecting the request to the Controller.
  2. The Controller respond to the request, do logic and sends response to a view.
  3. The View takes these parameters and presents them in a view to the user.
And as you already might have guessed the above request and response parameters is the Model in the MVC pattern.

Before continue I would like to stress a few things extra hard:
  • All graphical components are created and layed out in the view. Nowhere else!
  • The Controller is neutral to whatever graphical user interface technique used! A simple control question is. Think that you must change graphical technique, from example Swing to SVT. Will you controller classes be affected? This question might feel quite theoretical but is a good eye opener if you have successfully separated the view concern from the controller concern.
  • The Model should also be plain old Java object, POJO, and not infected with presentation specific codes. And again check that by asking yourself if you were forced to change presentation technique. How would that affect your model classes?
And a final reminder about the model in the MVC pattern and the model in Swing. The Swing model should not be confused with the model in the MVC pattern. The Swing model is entirely part of the choosen presentation technique, i.e. Swing and belongs only in the view and should not be mixed with the model in the MVC pattern.

The last thing before diving into concrete code is the concern of Object Instances Lifecycle. Now you might start to wonder what that has to do with MVC. And the question is none, but the problem is still real and I think one must adress this with some thinking and strategy, because it is so vital to the application and programmers daily life. And not to mention testability. Which is something I will not go into any deeper in this blog, but I can recommned the FEST test framework. Anyhow which Swing test framework you choose you must before have a clear lifecycle handle of object in your rich client. Otherwise you will end up with untestable code.

So lest start with the View. What does a view do?
  1. Layout the graphical components.
  2. Responds to user interaction (and sends the request to the Controller).
  3. Updates the view (requested from the Controller).
Lets starts with the layout.

package se.msc.examples.mvcframework;

import javax.swing.JComponent;

public abstract class AbstractView<c extends JComponent> {
    private final AbstractFrame mainFrame;
    private final C contentPane;

    public AbstractView(AbstractFrame mainFrame) {
        this.mainFrame = mainFrame;
        this.contentPane = layout();
    }

    protected abstract C layout();

    protected AbstractFrame getMainFrame() {
        return mainFrame;
    }

    public C getContentPane() {
        return contentPane;
    }
}


package se.msc.examples.demo.view;

import static se.msc.examples.mvcframework.ComponentFactory.button;
import static se.msc.examples.mvcframework.ComponentFactory.buttons;
import static se.msc.examples.mvcframework.ComponentFactory.panel;
import static se.msc.examples.mvcframework.ComponentFactory.table;

import java.awt.BorderLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;

import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JTable;

import se.msc.examples.demo.controller.PersonController;
import se.msc.examples.demo.model.Person;
import se.msc.examples.mvcframework.AbstractFrame;
import se.msc.examples.mvcframework.AbstractView;
import se.msc.examples.mvcframework.BeanTableModel;

public class PersonListView extends AbstractView<jpanel> {
    private BeanTableModel<person> tableModel;
    private JTable table;

    public PersonListView(AbstractFrame mainFrame) {
        super(mainFrame);
    }

    @Override
    protected JPanel layout() {
        tableModel = new BeanTableModel<person>(Person.class, new String[] { "Id", "Created", "Name" });
        table = table(tableModel);

        JPanel panel = panel(new BorderLayout(), null);
        panel.add(new JScrollPane(table), BorderLayout.CENTER);
        panel.add(buttons(button("Create", new CreatePerson()), button("Retrieve", new RetrievePerson()), button("Delete", new DeletePerson())), BorderLayout.SOUTH);
        return panel;
    }

    // ------------ Request Code Goes Here

    // ------------ Response Code Goes Here

    
    // ------------ Test Code Goes Here
        
    public static void main(String[] args) {
        javax.swing.SwingUtilities.invokeLater(new Runnable() {

            @Override
            public void run() {
                showFrame(frame("PersonListView", new PersonListView(null).getContentPane(), null));
            }
        });
    }   
}


Try not to bother about the static component methods, such as frame, etc. they are all methods in a ComponentFactory that do all the Swing plumbing construction. I will later show them in the Appendix.

But our application will certaintly contains several views, so lets we refactor out the main method in a class thats holds the JFrame instance. What we also will do in our frame class is to solve the problem of object lifecycle and dependencies to those. We will create a map that hold all the views and another that holds the controllers. And finally we will inject the instance of the main frame to all views and controllers. In this way, we will have only one instance of all the views and controllers, but also they will be accessable everywhere.

package se.msc.examples.mvcframework;

import static se.msc.examples.mvcframework.ComponentFactory.showFrame;

import java.util.HashMap;
import java.util.Map;

import javax.swing.JComponent;
import javax.swing.JFrame;
import javax.swing.JPanel;

public abstract class AbstractFrame {
    protected final JFrame frame;
    protected final Map<Class<? extends AbstractView<? extends JComponent>>, AbstractView<? extends JComponent>> views = new HashMap<Class<? extends AbstractView<? extends JComponent>>, AbstractView<? extends JComponent>>();
    protected final Map<Class<? extends AbstractController>, AbstractController> controllers = new HashMap<Class<? extends AbstractController>, AbstractController>();

    public AbstractFrame() {
        registerAllViews();
        registerAllControllers();
        this.frame = layout();
    }

    protected abstract void registerAllViews();

    protected abstract void registerAllControllers();

    protected abstract JFrame layout();

    protected void show() {
        showFrame(frame);
    }

    @SuppressWarnings("unchecked")
    public <v extends AbstractView<? extends JComponent>> V getView(Class<v> viewClass) {
        return (V) views.get(viewClass);
    }

    @SuppressWarnings("unchecked")
    public <v extends AbstractView<JPanel>> InternalFrameView<v> getInternalFrameView(Class<v> viewClass) {
        return (InternalFrameView<v>) views.get(viewClass);
    }

    @SuppressWarnings("unchecked")
    public <c extends AbstractController> C getController(Class<c> controllerClass) {
        return (C) controllers.get(controllerClass);
    }
}


package se.msc.examples.demo.main;

import static se.msc.examples.mvcframework.ComponentFactory.frame;

import javax.swing.JFrame;

import se.msc.examples.demo.controller.PersonController;
import se.msc.examples.demo.view.PersonFormView;
import se.msc.examples.demo.view.PersonListView;
import se.msc.examples.mvcframework.AbstractFrame;

public class MainFrame extends AbstractFrame {

    @Override
    protected void registerAllViews() {
        views.put(PersonListView.class, new PersonListView(this));
    }

    @Override
    protected void registerAllControllers() {
        controllers.put(PersonController.class, new PersonController(this));
    }

    @Override
    protected JFrame layout() {
        return frame("Demo MVC Framework", getView(PersonFormView.class).getContentPane(), null);
    }

    public static void main(String[] args) {
        javax.swing.SwingUtilities.invokeLater(new Runnable() {

            @Override
            public void run() {
                new MainFrame().show();
            }
        });
    }
}


Now lets get back to the View and add code for the user interaction request.

package se.msc.examples.demo.view;

import static se.msc.examples.mvcframework.ComponentFactory.button;
import static se.msc.examples.mvcframework.ComponentFactory.buttons;
import static se.msc.examples.mvcframework.ComponentFactory.frame;
import static se.msc.examples.mvcframework.ComponentFactory.panel;
import static se.msc.examples.mvcframework.ComponentFactory.showFrame;
import static se.msc.examples.mvcframework.ComponentFactory.table;

import java.awt.BorderLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;

import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JTable;

import se.msc.examples.demo.controller.PersonController;
import se.msc.examples.demo.model.Person;
import se.msc.examples.mvcframework.AbstractFrame;
import se.msc.examples.mvcframework.AbstractView;
import se.msc.examples.mvcframework.BeanTableModel;

public class PersonListView extends AbstractView<jpanel> {
    private BeanTableModel<person> tableModel;
    private JTable table;

    public PersonListView(AbstractFrame mainFrame) {
        super(mainFrame);
    }

    @Override
    protected JPanel layout() {
        tableModel = new BeanTableModel<person>(Person.class, new String[] { "Id", "Created", "Name" });
        table = table(tableModel);

        JPanel panel = panel(new BorderLayout(), null);
        panel.add(new JScrollPane(table), BorderLayout.CENTER);
        panel.add(buttons(button("Create", new CreatePerson()), button("Retrieve", new RetrievePerson()), button("Delete", new DeletePerson())), BorderLayout.SOUTH);
        return panel;
    }

    // ------------ Request Code Goes Here

    private class CreatePerson implements ActionListener {

        @Override
        public void actionPerformed(ActionEvent e) {
            getMainFrame().getController(PersonController.class).preCreatePerson();
        }
    }

    private class RetrievePerson implements ActionListener {

        @Override
        public void actionPerformed(ActionEvent e) {
            Person person = tableModel.getBean(table.getSelectedRow());
            getMainFrame().getController(PersonController.class).retrievePerson(person);
        }
    }

    private class DeletePerson implements ActionListener {

        @Override
        public void actionPerformed(ActionEvent e) {
            Person person = tableModel.getBean(table.getSelectedRow());
            getMainFrame().getController(PersonController.class).deletePerson(person);
        }
    }

    // ------------ Response Code Goes Here

    public void addPerson(Person person) {
        tableModel.insert(person);
    }

    public void updatePerson(Person person) {
        tableModel.setBean(table.getSelectedRow(), person);
    }

    public void deletePerson() {
        tableModel.remove(table.getSelectedRow());
    }
    
    // ------------ Test Code Goes Here
        
    public static void main(String[] args) {
        javax.swing.SwingUtilities.invokeLater(new Runnable() {

            @Override
            public void run() {
                showFrame(frame("PersonListView", new PersonListView(null).getContentPane(), null));
            }
        });
    }    
}


As you can see, it is easy to get instance references to the controller class, but the controller is also used in a type safe way. This approach have several advances compared with external configuration files where you wire views and controllers together through weak string references. What will happen if decide to refactor and change a controller method name?

The view can in the same manner be accessed in the same type safe way. To need for external configuration files, such as XML files.

So this is the basic of MVC. Now look if this holds for more complex GUI. Lets say we want to make a desktop application. Well the layout stays the same, but we need to put theirs panel in internal frame. We could do that in the view, but what happens if requirement changes and these views wants to put in a tabbed panel instead. No lets leave the views intact and instead lets created a decorating view that takes a view of panel as argument.

package se.msc.examples.mvcframework;

import static se.msc.examples.mvcframework.ComponentFactory.internalFrame;

import javax.swing.JDesktopPane;
import javax.swing.JInternalFrame;
import javax.swing.JPanel;

public class InternalFrameView<v extends AbstractView<JPanel>> extends AbstractView<jinternalframe> {
    private final JDesktopPane desktopPane;
    private final V view;
    private final JInternalFrame internalFrame;

    public InternalFrameView(AbstractFrame mainFrame, JDesktopPane desktopPane, V view) {
        super(mainFrame);
        this.desktopPane = desktopPane;
        this.view = view;
        this.internalFrame = internalFrame(view.getContentPane(), view.getClass().getSimpleName());
    }

    @Override
    protected JInternalFrame layout() {
        return internalFrame;
    }

    // ------------ Request Code Goes Here

    // ------------ Response Code Goes Here

    public V getView() {
        return view;
    }

    public void show() {
        desktopPane.add(internalFrame);
        internalFrame.pack();
        internalFrame.setVisible(true);
        try {
            internalFrame.setSelected(true);
        } catch (java.beans.PropertyVetoException e) {
        }
    }

    public void close() {
        internalFrame.dispose();
        desktopPane.remove(internalFrame);
    }
}


Then we need to modify our main frame class.

package se.msc.examples.demo.main;

import static se.msc.examples.mvcframework.ComponentFactory.frame;

import javax.swing.JDesktopPane;
import javax.swing.JFrame;

import se.msc.examples.demo.controller.PersonController;
import se.msc.examples.demo.view.PersonFormView;
import se.msc.examples.demo.view.PersonListView;
import se.msc.examples.demo.view.PersonTreeView;
import se.msc.examples.mvcframework.AbstractFrame;
import se.msc.examples.mvcframework.InternalFrameView;

public class MainInternalFrame extends AbstractFrame {
    private JDesktopPane desktopPane;

    @Override
    protected void registerAllViews() {
        desktopPane = new JDesktopPane();
        views.put(PersonListView.class, new InternalFrameView<personlistview>(this, desktopPane, new PersonListView(this)));
        views.put(PersonFormView.class, new InternalFrameView<personformview>(this, desktopPane, new PersonFormView(this)));
    }

    @Override
    protected void registerAllControllers() {
        controllers.put(PersonController.class, new PersonController(this));
    }

    @Override
    protected JFrame layout() {
        return frame("Demo MVC Framework", desktopPane, new ToolBarView(this).getContentPane());
    }

    public static void main(String[] args) {
        javax.swing.SwingUtilities.invokeLater(new Runnable() {

            public void run() {
                new MainInternalFrame().show();
            }
        });
    }
}


package se.msc.examples.demo.view;

import static se.msc.examples.mvcframework.ComponentFactory.button;
import static se.msc.examples.mvcframework.ComponentFactory.buttons;
import static se.msc.examples.mvcframework.ComponentFactory.frame;
import static se.msc.examples.mvcframework.ComponentFactory.panel;
import static se.msc.examples.mvcframework.ComponentFactory.showFrame;
import static se.msc.examples.mvcframework.ComponentFactory.table;

import java.awt.BorderLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;

import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JTable;

import se.msc.examples.demo.controller.PersonController;
import se.msc.examples.demo.model.Person;
import se.msc.examples.mvcframework.AbstractFrame;
import se.msc.examples.mvcframework.AbstractView;
import se.msc.examples.mvcframework.BeanTableModel;

public class PersonListView extends AbstractView<jpanel> {
    private BeanTableModel<person> tableModel;
    private JTable table;

    public PersonListView(AbstractFrame mainFrame) {
        super(mainFrame);
    }

    @Override
    protected JPanel layout() {
        tableModel = new BeanTableModel<person>(Person.class, new String[] { "Id", "Created", "Name" });
        table = table(tableModel);

        JPanel panel = panel(new BorderLayout(), null);
        panel.add(new JScrollPane(table), BorderLayout.CENTER);
        panel.add(buttons(button("Create", new CreatePerson()), button("Retrieve", new RetrievePerson()), button("Delete", new DeletePerson())), BorderLayout.SOUTH);
        return panel;
    }

    // ------------ Request Code Goes Here

    private class CreatePerson implements ActionListener {

        @Override
        public void actionPerformed(ActionEvent e) {
            getMainFrame().getController(PersonController.class).preCreatePerson();
        }
    }

    private class RetrievePerson implements ActionListener {

        @Override
        public void actionPerformed(ActionEvent e) {
            Person person = tableModel.getBean(table.getSelectedRow());
            getMainFrame().getController(PersonController.class).retrievePerson(person);
        }
    }

    private class DeletePerson implements ActionListener {

        @Override
        public void actionPerformed(ActionEvent e) {
            Person person = tableModel.getBean(table.getSelectedRow());
            getMainFrame().getController(PersonController.class).deletePerson(person);
        }
    }

    // ------------ Response Code Goes Here

    public void addPerson(Person person) {
        tableModel.insert(person);
    }

    public void updatePerson(Person person) {
        tableModel.setBean(table.getSelectedRow(), person);
    }

    public void deletePerson() {
        tableModel.remove(table.getSelectedRow());
    }
    
    // ------------ Test Code Goes Here
        
    public static void main(String[] args) {
        javax.swing.SwingUtilities.invokeLater(new Runnable() {

            @Override
            public void run() {
                showFrame(frame("PersonListView", new PersonListView(null).getContentPane(), null));
            }
        });
    }    
}


package se.msc.examples.demo.view;

import static se.msc.examples.mvcframework.ComponentFactory.button;
import static se.msc.examples.mvcframework.ComponentFactory.frame;
import static se.msc.examples.mvcframework.ComponentFactory.label;
import static se.msc.examples.mvcframework.ComponentFactory.showFrame;
import static se.msc.examples.mvcframework.ComponentFactory.textField;

import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.text.SimpleDateFormat;

import javax.swing.JPanel;
import javax.swing.JTextField;

import se.msc.examples.demo.controller.PersonController;
import se.msc.examples.demo.model.Person;
import se.msc.examples.mvcframework.AbstractFrame;
import se.msc.examples.mvcframework.AbstractView;

import com.jgoodies.forms.builder.PanelBuilder;
import com.jgoodies.forms.factories.ButtonBarFactory;
import com.jgoodies.forms.layout.CellConstraints;
import com.jgoodies.forms.layout.FormLayout;

public class PersonFormView extends AbstractView<jpanel> {
    private JTextField id;
    private JTextField created;
    private JTextField name;
    private Person person;

    public PersonFormView(AbstractFrame mainFrame) {
        super(mainFrame);
    }

    // ------------ Request Code Goes Here

    @Override
    protected JPanel layout() {
        FormLayout formLayout = new FormLayout("right:pref, 2dlu, pref:grow", // columns
                "pref, 3dlu, pref, 3dlu, pref, 3dlu, pref"); // rows
        PanelBuilder builder = new PanelBuilder(formLayout);
        builder.setDefaultDialogBorder();
        CellConstraints cc = new CellConstraints();

        builder.add(label("Id:", null), cc.xy(1, 1));
        builder.add(id = textField(10, false), cc.xy(3, 1));

        builder.add(label("Created:", null), cc.xy(1, 3));
        builder.add(created = textField(10, false), cc.xy(3, 3));

        builder.add(label("Name:", null), cc.xy(1, 5));
        builder.add(name = textField(10, true), cc.xy(3, 5));

        JPanel pnlSaveCancelButtons = ButtonBarFactory.buildOKCancelBar(button("Save", new Save()), button("Cancel", new Cancel()));
        builder.add(pnlSaveCancelButtons, cc.xy(3, 7, "left, center"));

        return builder.getPanel();
    }

    private class Cancel implements ActionListener {

        @Override
        public void actionPerformed(ActionEvent e) {
            getMainFrame().getController(PersonController.class).cancelCreatePerson();
        }
    }

    private class Save implements ActionListener {

        @Override
        public void actionPerformed(ActionEvent e) {
            getMainFrame().getController(PersonController.class).savePerson(getValues());
        }
    }

    // ------------ Response Code Goes Here

    public Person getValues() {
        person.setName(name.getText());
        return person;
    }

    public void setValues(Person person) {
        this.person = person;
        if (person.getId() != null) id.setText(new String(person.getId()));
        else id.setText("");
        if (person.getCreated() != null) created.setText(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(person.getCreated()));
        else created.setText("");
        if (person.getName() != null) name.setText(new String(person.getName()));
        else name.setText("");
    }

    // ------------ Test Code Goes Here

    public static void main(String[] args) {
        javax.swing.SwingUtilities.invokeLater(new Runnable() {

            @Override
            public void run() {
                showFrame(frame("PersonFormView", new PersonFormView(null).getContentPane(), null));
            }
        });
    }
}



package se.msc.examples.demo.controller;

import java.util.Date;
import java.util.UUID;

import se.msc.examples.demo.model.Person;
import se.msc.examples.demo.view.PersonFormView;
import se.msc.examples.demo.view.PersonListView;
import se.msc.examples.mvcframework.AbstractController;
import se.msc.examples.mvcframework.AbstractFrame;

public class PersonController extends AbstractController {

    public PersonController(AbstractFrame mainFrame) {
        super(mainFrame);
    }

    public void savePerson(Person person) {
        if (person.getId() == null) postCreatePerson(person);
        else updatePerson(person);
    }

    // ------------ CRUD Code Goes Here

    public void preCreatePerson() {
        // 1. do server logic
        Person person = new Person();
        // 2. do swing response
        getMainFrame().getInternalFrameView(PersonFormView.class).getView().setValues(person);
        getMainFrame().getInternalFrameView(PersonFormView.class).show();
    }

    private void postCreatePerson(Person person) {
        // 1. do server logic
        person.setId(UUID.randomUUID().toString());
        person.setCreated(new Date());
        // 2. do swing response
        getMainFrame().getInternalFrameView(PersonFormView.class).close();
        getMainFrame().getInternalFrameView(PersonListView.class).getView().addPerson(person);
    }

    public void cancelCreatePerson() {
        getMainFrame().getInternalFrameView(PersonFormView.class).close();
    }

    public void retrievePerson(Person person) {
        // 1. do server logic
        // 2. do swing response
        getMainFrame().getInternalFrameView(PersonFormView.class).getView().setValues(person);
        getMainFrame().getInternalFrameView(PersonFormView.class).show();
    }

    private void updatePerson(Person person) {
        // 1. do server logic
        // 2. do swing response
        getMainFrame().getInternalFrameView(PersonFormView.class).close();
        getMainFrame().getInternalFrameView(PersonListView.class).getView().updatePerson(person);
    }

    public void deletePerson(Person person) {
        // 1. do server logic
        // 2. do swing response
        getMainFrame().getInternalFrameView(PersonListView.class).getView().deletePerson();
    }
}


Conclusion
MVC greatly reducing the coupling between seperated classes, but doing right is not always easy.

The complete source code from https://sourceforge.net/projects/swingframework/files/.

November 22, 2011

How to Install Swedish BankID on Ubuntu 11.10 64-bits

First download the Swedish BankID program from https://install.bankid.com/.

Unzip the file.
$ tar -xvf BISP-.tar.gz

Install BankID
$ cd BISP-
sudo ./install..sh -i

The last part is only for 64-bit OS and that is to simulate your 32-bit Firefox pluging to run on your 64-bit OS. To do that you need the ia32-libs and nspluginwrapper packages. On a clean installed computer they are not installed.

To install them
$ sudo apt-get install ia32-libs nspluginwrapper

Then install the BankID plugin in Firefox.
$ sudo nspluginwrapper -i /usr/local/lib/personal/libplugins.so

Now restart Firefox and test your plugin via https://install.bankid.com/

To read more in depth of the installation, see http://ubuntu-se.org/wiki/NexusPersonal.

November 21, 2011

Can Programmers Learn Something from Industrial Design?

Another interest of mine is design and art. Which I will not write more about on this technical blog, but I recently got aquinted with the designer Dieter Rams (http://en.wikipedia.org/wiki/Dieter_Rams) who is interesting in many ways, but one thing that I think is of interest to share on this blog is his famous:

Ten Principles for Good Desing


1. Good design is innovative.

2. Good design makes a product useful.

3. Good design is aesthetic.

4. Good desing makes a product understandable.

5. Good design is unobtrusive.

6. Good design is honest.

7. Good design is long-lasting.

8. Good desing is thorough, down to the last detail.

9. Good design is environmentally-friendly.

10. Good design is as little design as possible.

Where my favourite is the last ”Good design is as little design as possible.”. Like James Bond would have said it ”Less is more.” Or maybe as a programmers would have put it ”KISS”.

You can read more of these principles here http://www.vitsoe.com/en/gb/about/dieterrams/gooddesign.

Is Oracle Making any Progress with Pushing Java Forword?

Well in one sense, Oracle have managed to release a new Java version 7, but there are still some differences to solve to make the work go faster and smother.

Read more about the Java 7 ballot
http://jcp.org/en/jsr/results?id=5111

and for the Java 8 ballot release.
http://jcp.org/en/jsr/results?id=5112

One can clearly see that the fighting of lawyers are still going on for the license of Java. I hope they solve it and comes to an agrement so that the evolving of Java progresses faster, than it has since Java 5.

For history of previous Java releases, please read
http://magnus-k-karlsson.blogspot.com/2010/10/jaoo-day-4-java-future-at-oracle.html

How to Disable the Overlay Scrollbar in Ubuntu 11.01 and 11.10

How to Disable the Overlay Scrollbar in Ubuntu 11.01 and 11.10

I like the new look of Unity, but there is one thing that keeps bugging me and that is the new Overlay Scrollbar. To remove it, do the following from the Terminal.

First you need to become root
$ sudo su

Then export the following setting:
$ echo export LIBOVERLAY_SCROLLBAR=0 > /etc/X11/Xsession.d/80overlayscrollbars

Last reboot and voilà the classical scrollbar is back.

February 28, 2011

Java Bug hangs the JVM

I'm not sure about publishing bugs, but I guess it's already out there and there is also a patch available. The problem is in java.lang.Double and the handling of maximum doouble value, i.e. 2.2250738585072012e-308. The following code will send the older JVM to an infinitive loop.
Double.parseDouble(2.2250738585072012e-308)

Ok, but is this really a problem? Yes, it is for Java server connected to Internet. Let's imagine a Bank Server written in Java and exposed/available from the Internet. And you can be quite certain there will some fields taking double as input. These html fields are all Strings, but when processing at the server they are parsed to Double. And this is a typical exploit scenario a hacker can use for hanging the server/DoS attack.

The solution is either patching your current JVM version or upgrading to the latest JVM, i.e. Java 6 Update 24.


http://blogs.oracle.com/security/2011/02/security_alert_for_cve-2010-44.html

This bug is foremost for server application, since desktop application can always be restarted, but if you want to upgrade your desktop java version, please go to http://java.com/.

February 23, 2011

Best Practice Aspect-Oriented Programming with JBoss AOP

In my recent project I have been working with JBoss AOP. There have been some pitfalls, I have fallen into and in this blog I will share those you.

First of all. Do not choose aspect-oriented programming to solve everyday Java problems. Example of good cases are:
  • Logging
  • Caching
  • Security
  • Error Handling
But why does AOP does not suites to solve common Java problem? Lets look at an example with Spring and AspectJ.

The Logic class:
public class FooPojo { public void setFoo(Object inparam) { /* logic goes here... */ } }
The Aspect class:
@Aspect public class FooAspect {
    @Before("execution(void set*(*))") public void handle() { /* logic goes here... */ }
}
Boilerplate XML configuration files
<beans>
    <aop:aspectj-autoproxy>
        <aop:include name="fooAspect" />
    </aop:aspectj-autoproxy>
    <bean id="fooAspect" class="se.msc.example.aop.FooAspect" />
</beans>

<beans>
    <import resource= "aspects-config.xml"/>
    <bean name="foo" class="se.msc.example.aop.FooPojo" />
</beans>


And now call it
((FooPojo) new ClassPathXmlApplicationContext(“application-config.xml”).getBean(“foo”)).setFoo(null);
The problem with the code above, is that in FooPojo there is no hint at all, that other code will be called. This can be very confusing for a junior programmer and also to a much more skilled programmer that is not familiar with AOP. So how to make AOP more clearer and more understandable? The answer is to look at other framework and how they have solved it. Take for example Spring. They use AOP very heavily under the hood, to solve common tasks as marking classes as transactional (@Transactional). And in J2EE, Oracle uses also Annotation, e.g. in JAX-WS they have the method Annotation @WebMethod, to signal that a method is a Web Service method. So lets copy that pattern, to write your own Annotation, as marker in the code that you want to apply apsect-oriented programming to. Our own Annotation, to trigger Aspect:
package se.msc.example.aop;

import static java.lang.annotation.ElementType.METHOD;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
 * Cache Annotation.
 */
@Retention(RetentionPolicy.RUNTIME)
@Target({ METHOD })
public @interface Cache {

 String attribute() default "";
    // String value() default ""; if attribute name is 'value', you do not need to specify it 
}

Our Aspect. Here we use interface implementation. This solution works on JBoss 4.3.0 - 5.1.0:
package se.msc.example.aop;

import java.lang.annotation.Annotation;
import java.util.concurrent.ConcurrentHashMap;

import org.apache.log4j.Logger;
import org.jboss.aop.advice.Interceptor;
import org.jboss.aop.joinpoint.Invocation;
import org.jboss.aop.joinpoint.MethodInvocation;

/**
 * Cache interceptor demo. See also jboss-aop.xml. 
 */
public class CacheInterceptor implements Interceptor {
 protected final Logger log = Logger.getLogger(this.getClass());
 private final ConcurrentHashMap<Long, Object> cache = new ConcurrentHashMap<Long, Object>();
 
 /**
  * @see org.jboss.aop.advice.Interceptor#getName()
  */
 public String getName() {
  return this.getClass().getSimpleName();
 }

 /**
  * @see org.jboss.aop.advice.Interceptor#invoke(Invocation)
  */
 public Object invoke(Invocation invocation) throws Throwable {
  Object rtnObject = beforeInvocation((MethodInvocation) invocation);
  if (rtnObject != null) {
   if (log.isDebugEnabled()) log.debug("CACHED.");
   return rtnObject; // if cached, return 
  }
  try {
   rtnObject = invocation.invokeNext();
   afterInvocation((MethodInvocation) invocation, rtnObject);
  } catch (Throwable t) {
   onInvocationException((MethodInvocation) invocation, t);
  }
  return rtnObject;
 }

 protected Object beforeInvocation(MethodInvocation invocation) throws Throwable {
  if (log.isDebugEnabled()) log.debug("beforeInvocation...");
  Object[] args = invocation.getArguments();
  // always check for null and array length, when using reflection
  if (args.length > 0 && args[0] != null && (args[0] instanceof Long)) return cache.get(args[0]);
  return null;
 }

 protected void afterInvocation(MethodInvocation invocation, Object rtnObject) throws Throwable {
  if (log.isDebugEnabled()) log.debug("afterInvocation...");
  if (rtnObject == null) return;
  Object[] args = invocation.getArguments();
  // always check for null and array length, when using reflection
  if (args.length > 0 && args[0] != null && (args[0] instanceof Long)) {
   if (log.isDebugEnabled()) log.debug("ADD id=" + args[0] + ", object='" + rtnObject + "'.");
   cache.put((Long) args[0], rtnObject);
  }
 }

 protected void onInvocationException(MethodInvocation invocation, Throwable invocationException)
         throws Throwable {
  if (log.isDebugEnabled()) log.debug("onInvocationException...");
  log.error("onInvocationException", invocationException);
  // if nothing we can do, re-throw
  throw invocationException;
 }
 
 // ----------------------- Helper Method -----------------------
 
 protected <t extends Annotation> T getMethodAnnotation(MethodInvocation invocation,
         Class<t> annotationClass) {
  return invocation.getMethod().getAnnotation(annotationClass);
 } 
}

The JBoss AOP configuration file, META-INF/jboss-aop.xml
<?xml version="1.0" encoding="UTF-8"?>
<aop xmlns="urn:jboss:aop-beans:1.0">
 <!-- Singleton Interceptor -->
 <!-- http://docs.jboss.com/aop/1.3/aspect-framework/reference/en/html_single/index.html -->
 <interceptor name="Cache" class="se.msc.example.aop.CacheInterceptor" scope="PER_VM" />
 <bind pointcut="all(@se.msc.example.aop.Cache)">
  <interceptor-ref name="Cache" />
 </bind>
</aop>

Now lets create a Test class, that we annotate with our own Annotation:
package se.msc.example.aop;

import java.util.HashMap;
import java.util.Map;

/**
 * Simulate DB Service.
 */
public class Dummy {
 private Map<Long, String> db = new HashMap<Long, String>();
 
 @Cache
 public String load(Long id) {
  if (!db.containsKey(id)) db.put(id, "NEW " + id);
  return db.get(id);
 }
 
 public void update(long id, String str) {
  db.put(id, str);
 }
}

And a Unit Test to verify it is working.
package se.msc.example.aop;

import java.util.HashMap;
import java.util.Map;

/**
 * Simulate DB Service.
 */
public class Dummy {
 private Map<Long, String> db = new HashMap<Long, String>();
 
 @Cache
 public String load(Long id) {
  if (!db.containsKey(id)) db.put(id, "NEW " + id);
  return db.get(id);
 }
 
 public void update(long id, String str) {
  db.put(id, str);
 }
}

And here is the maven pom.xml
<?xml version="1.0" encoding="UTF-8" ?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
 xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
 <modelversion>4.0.0</modelVersion>
 <groupid>se.msc.example</groupId>
 <artifactid>aop-demo</artifactId>
 <version>1.0-SNAPSHOT</version>
 <packaging>jar</packaging>
 <properties>
  <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
  <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
  <jbossaop.version>2.1.1.GA</jbossaop.version>
 </properties>
 <repositories>
  <repository>
   <id>repository.jboss.org</id>
   <name>Jboss Repository for Maven</name>
   <url>http://repository.jboss.org/maven2/</url>
  </repository>
 </repositories>
 <pluginrepositories>
  <pluginrepository>
   <id>repository.jboss.org</id>
   <name>Jboss Repository for Maven</name>
   <url>http://repository.jboss.org/maven2/</url>
  </pluginRepository>
 </pluginRepositories>
 <dependencies>
  <!-- JBoss 5.1.0.GA -->
  <dependency>
   <groupid>org.jboss.aop</groupId>
   <artifactid>jboss-aop</artifactId>
   <version>2.1.1.GA</version>
  </dependency>
  <!-- Test support -->
  <dependency>
   <groupid>junit</groupId>
   <artifactid>junit</artifactId>
   <version>4.8.1</version>
   <scope>test</scope>
  </dependency>
 </dependencies>
 <build>
  <pluginmanagement>
   <plugins>
    <plugin>
     <groupid>org.apache.maven.plugins</groupId>
     <artifactid>maven-compiler-plugin</artifactId>
     <version>2.0.2</version>
     <configuration>
      <source>1.6</source>
      <target>1.6</target>
      <!-- Plugin ignores default value 'project.build.sourceEncoding' -->
      <encoding>${project.build.sourceEncoding}</encoding>
     </configuration>
    </plugin>
    <plugin>
     <groupid>org.apache.maven.plugins</groupId>
     <artifactid>maven-jar-plugin</artifactId>
     <version>2.2</version>
     <configuration>
      <archive>
       <manifest>
        <!-- Adding Implementation And Specification Details -->
        <adddefaultimplementationentries>true</addDefaultImplementationEntries>
       </manifest>
      </archive>
     </configuration>
    </plugin>
    <plugin>
     <groupid>org.apache.maven.plugins</groupId>
     <artifactid>maven-surefire-plugin</artifactId>
     <version>2.4.3</version>
     <configuration>
      <!-- Konfiguration för AOP loadtime-weaving -->
      <argline>-javaagent:"${settings.localRepository}/org/jboss/aop/jboss-aop/${jbossaop.version}/jboss-aop-${jbossaop.version}.jar"</argLine>
     </configuration>
    </plugin>    
   </plugins>
  </pluginManagement>
 </build>
 <reporting>
  <plugins>
   <plugin>
    <groupid>org.codehaus.mojo</groupId>
    <artifactid>cobertura-maven-plugin</artifactId>
    <version>2.3</version>
   </plugin>
  </plugins>
 </reporting>
</project>

To make the example complete, we also need to supply a log4j configuration file.
<?xml version="1.0" encoding="UTF-8"?> 
<!DOCTYPE log4j:configuration PUBLIC "-//log4j/log4j Configuration//EN" "log4j.dtd">
<log4j:configuration xmlns:log4j="http://jakarta.apache.org/log4j/">
 <appender name="CONSOLE" class="org.apache.log4j.ConsoleAppender">
  <param name="Target" value="System.out" />
  <layout class="org.apache.log4j.PatternLayout">
   <param name="ConversionPattern" value="%d{yyyy-MM-dd HH:mm:ss.SSS} [%p] %c:%L - %m%n" />
  </layout>
 </appender>
 <appender name="FILE_APPENDER" class="org.apache.log4j.RollingFileAppender">
  <param name="File" value="/tmp/server.log" />
  <param name="Append" value="true" />
  <layout class="org.apache.log4j.PatternLayout">
   <param name="ConversionPattern" value="%d %-5p [%t] %C{2} (%F:%L) - %m%n" />
  </layout>
 </appender>
 <logger name="se.msc.example.aop">
  <level value="DEBUG" />
  <appender-ref ref="CONSOLE" />
 </logger>
 <root>
  <level value="WARN" />
  <appender-ref ref="CONSOLE" />
 </root>
</log4j:configuration>


To run/debug this inside Eclipse we need to copy the argLine from the maven pom file to the unit test file configuration.



For more about JBoss AOP Maven plugin , see http://community.jboss.org/wiki/JBossAOPMavenPlugin.