Skip to main content

Swing and Roundabouts 3: Framewarez

Posted by evanx on July 13, 2006 at 6:29 AM PDT

gold3_200.jpg

beer_jug.jpg

beer1.JPG

"No, that's not the worst excuse I ever thought up." Homer Simpson

newsbiere.jpg align=left vspace=4 hspace=16 border=0 />

trappistcorked.jpg align=left vspace=4 hspace=16 border=0 />

beer5_250l.jpg

beer_bottle_250.jpg align=left vspace=4 hspace=16 border=0 />

beer5_275l.jpg beer5_300.jpg align="left" hspace="16 "/>

bottle_150.jpg align=left vspace=4 hspace=16 border=0 />

"The problem with the world is that everyone is a few drinks behind." Humprey Bogart.

"You heard me; I won't be in for the rest of the week... I told you! My baby beat me up! ... No, it is not the worst excuse I ever thought up."
Homer Simpson

"They who drink beer will think beer." Washington Irving

"The answer to life's problems aren't at the bottom of a beer bottle, they're on TV."
Homer Simpson.

"I've figured out an alternative to giving up my beer. Basically, we become a family of traveling acrobats." Homer Simpson

"There's nothing wrong with sobriety in moderation."
John Ciandi

"When I read about the evils of drinking, I gave up reading."
Henny Youngman.

"He was a wise man who invented beer." Plato.

"When I read about the evils of beer, I gave up reading." Henny Youngman

Swing and Roundabouts 3: Framewarez

We might use the spreadsheet paradigm for our Swing application, that is, where
our "screens" are worksheets, and we can switch between worksheets using a tabbed pane.
We have a master menu and tool bar for opening new worksheets. This is our "menu system"
in legacy-speak. Each worksheet has a customised CURDified tool bar, eg. with actions like
New, Find, Delete, Save, Undo, Close. So let's get us some framewarez, like a JFrame
with a JMenuBar and JTabbedPane, to plug our JPanel worksheets into.

"I can resist everything except temptation." Oscar Wilde
-->


Prequels

"The People can be depended upon to meet any national crisis. Just bring them the real facts, and beer." Abraham Lincoln


See preceeding Swing articles Event DTs,
Turn Tables,
Panel Beater
and Inside Action.
(Excuse my aggressive refactoring of article names, eg. from Going into Action to Inside Action.)

gold3_crop.jpg align=left vspace=4 hspace=8 border=0 />
Disclaimer: As always, this is a "noisy" article (because i like writing noisy articles and i wrote this article, so...),
but luckily the noise is clearly demarcated using italicalised text this time. So please feel free to skip such text.


Introduction

"Beer is proof that God loves us and wants us to be happy." Benjamin Franklin


Our monastery operates a brewery. Our highly popular
"Dosy Abbot" ale has kept us in food and, um, ale, for the past 368 years.

So we need to keep track of our ingredients, products, orders, and what-not.
Now some of us newer younger monks are interested in computers,
in addition to beer of course. And we got the go-ahead from the Abbot
to write a stock control system, woohoo! So we wrote this Swing desktop app.
And I got nominated to write this article.

So our application has of a whole bunch of CURD worksheets, eg. for editing products,
product categories, suppliers, customers, etcetera. Then we have to
capture and view transactions, representing stock movements and related financial
documents, eg. purchase orders, invoices, delivery notes, stock transfers, stock takes,
etcetera.

So we need to assemble all these worksheets into an application framework, with access control.
In the first instance, the user should login. Then we display the "menu system" to enable
the user to launch "worksheets." Users typically have limited access, ie. to a specific
subset of worksheets. For example,
it's not a good idea to give the Abbot access to everything because a little bit of
knowledge is a very dangerous thing. But stock transfers to our pantry,
and the shrinkage report on our finished products, is for the Abbot's eyes only.

The design presented here is an improved sugar-coated redesign of the "access system"
of aptframework.dev.java.net.


Worksheet Framewarez

"Beer... Now there's a temporary solution." Homer Simpson


Before we wrote our brewery warez,
we used a spreadsheet to manage the monastery. (And we still do, to tell the truth.)
So when the Abbot OK'ed us writing a computer program to do the same thing,
he "requested" that we stick to the spreadsheet metaphor.

So we call our
"programs" worksheets, and we can open any number of worksheets as
tabs at the bottom of the screen, like a spreadsheet program.

menu.png

Our application framewarez is a JFrame, with a JMenuBar, and a JTabbedPane.
Our worksheets are launched from the menu. Worksheets render themselves as JPanel's, which we add
to the tabbed pane.

And here's a Web Start demo, woohoo! You can read about The Making Of this Web Starter
in next week's Trip and Tick 2 article, including why it runs outside of the sandbox,
and so is jarsigned, and see a Request for Help on sandboxing these thingymajigs.

webstart.small.gif
(695k, unsandboxed, Java5)


Defining the Menus

"Without question, the greatest invention in the history of mankind is beer.
The wheel does not go nearly as well with pizza." Dave Barry

We need to configure our menu items. That is, their labels, icons, keystrokes
and the associated worksheet to launch, eg. "Edit Product" launches ZProductWorksheet.

Our framework provides a GMenuConfigurator for us to extend as follows.

package org.trappist.belgium.brouwerij.menu;
...   
<b>public class</b> ZMenuConfigurator <b>extends</b> GMenuConfigurator {

    <i>... // system menu eg. lock screen, logout, exit </i>
   
    @MenuAnnotation(
        label = "Edit"
    )
    GMenuConfiguration topEdit = createMenu(<b>null</b>);
   
    @MenuAnnotation(
        label = "Edit Product",
        worksheet = ZProductWorksheet.<b>class</b>,
        icon = "yast_security",
        toolTip = "Edit products",
        ordinal = 2
    )
    GMenuConfiguration editProduct = createMenu(topEdit);
   
    <i>... // menus for all other worksheets in the application</i>

    @MenuAnnotation(
        label = "Help"
    )
    GMenuConfiguration topHelp = createMenu(<b>null</b>);
   
    @MenuAnnotation(
        label = "Online help",
        icon = "lifesaver",
        ordinal = 3
    )
    GMenuConfiguration helpOnlineHelp = createMenu(topHelp);
       
    @MenuAnnotation(
        label = "About",
        icon = "lightbulb",
        ordinal = 4
    )
    GMenuConfiguration helpAbout = createMenu(topHelp);
       
    <b>public</b> ZMenuConfigurator() {
        <b>super</b>();
        <b>super</b>.configure();
    }
}   

where the GMenuConfigurator superclass is implemented as follows.

package greenscreen.menu;
...   
<b>public class</b> GMenuConfigurator {
   
    <b>protected</b> List&lt;GMenuConfiguration&gt; menuList = <b>new</b> ArrayList();

    <b>protected</b> GMenuConfigurator() {
    }
   
    <b>protected</b> GMenuConfiguration createMenu(GMenuConfiguration parentMenu) {
        GMenuConfiguration menu = <b>new</b> GMenuConfiguration();
        menu.setParentMenu(parentMenu);
        menuList.add(menu);
        <b>return</b> menu;
    }
   
    <b>protected</b> <b>void</b> configure() {
        <i>... // read externalised list of MenuConfiguration objects</i>
        <b>for</b> (Field field : getClass().getFields()) {
            <b>if</b> (field.getType() == GMenuConfiguration.<b>class</b>) {
                field.setAccessible(<b>true</b>);
                configure(field, (GMenuConfiguration) field.get(<b>this</b>));          
            }
        }
    }
   
    <b>protected</b> <b>void</b> configure(Field field, GMenuConfiguration menu) {
        menu.setMenuId(field.getName());
        <i>... // configure using MenuAnnotation</i>
        <i>... // override with externalised configuration</i>
    }
   
    <b>public void</b> writeConfigurationFile(File configurationFile) {
        ...
    }

    <b>protected</b> <b>void</b> readConfigurationFile(File configurationFile) {
        ...
    }   
}   

where writeConfigurationFile() is invoked by the developer to generate
an externalised configuration file, as presented further below.


Leveraging the IDE for rapid prototyping

"Homer no function well without beer." Homer Simpson

Using Java code to capture defaults as above, enables us to leverage the IDE, eg. enjoy auto-completion
on the worksheet class names. We can take this further for icons, by generating
content for an "icon class" as follows.

<b>public class</b> GIconClassGenerator {
   ...
   <b>public void</b> generate(String iconDirectory) {
      <b>for</b> (String fileName : getFileNameList(iconDirectory)) {
         <b>if</b> (!ileName.endsWith(".png")) {
            String camelCaseFileName = toCamelCase(fileName);
            StringBuffer buffer = <b>new</b> StringBuffer();
            buffer.append("public final GIcon " + camelCaseFileName);
            buffer.append(" = createIcon("" + fileName + "");");        
            System.out.println(buffer);
         }
      }
   }
}

where getFileNameList() lists all the files in the given directory.


Externalising the Menu Configuration

"What I like to drink most is beer that belongs to others." Diogenes.

We adopt an approach where we code our defaults in the first instance,
for rapid prototyping, as in the above ZMenuConfigurator.
At any stage, we can then externalise these defaults,
eg. by invoking writeConfigurationFile() to emit content for a resource bundle,
and/or XML configuration file, in order to support translation and customisation.

When the application starts up, we load the configuration file to override
the coded defaults, eg. using readConfigurationFile() in the above GMenuConfigurator.

We read and parse the configuration data into configuration objects, eg. using JAXB2 to bind the following
configuration object to XML.

@XmlElement(name = "menu")
<b>public class</b> GMenuConfiguration {   
    @XmlAttribute <b>protected</b> GMenuConfiguration parentMenu;
    @XmlAttribute <b>protected</b> Class worksheetClass;
    @XmlAttribute <b>protected</b> String menuId;
    @XmlAttribute <b>protected</b> String keyStroke;
    @XmlAttribute <b>protected</b> Character mnemonic;
    @XmlAttribute <b>protected</b> String iconName;
    @XmlAttribute <b>protected</b> String label;
    @XmlAttribute <b>protected</b> String toolTip;
    @XmlAttribute <b>protected</b> Integer ordinal;
   
    <i>... // getters and setters</i>
    <i>... // configure(GMenuConfiguration), to overwrite with non-null properties from another instance</i>
}   


A diatribe on XML follows. I choose Java to program
the default configuration, because Java is more programmable, toolable
and beautiful than XML. So i love tools like JAXB2, to map XML to Java, using annotations,
so that i can program XML in Java. Which, as a Java programmer, i naturally prefer.

bottle_150b.jpg align=right vspace=4 hspace=16 border=0 />
Similarly with SQL, OQL et al, as addressed in
Bean Curd 2: The SQL.
And Bean Curd 2X will look at mapping XML to Java for the purposes of
programming XML queries in Java rather than XQuery. Anyway, i expect that Dolphin will
introduce querying into the language, in response to LINQ.


Users and roles

"All right, brain, let's back to killing you with beer." Homer Simpson

Since our worksheets are created by the developer, the menu configuration
ie. what worksheets we have available, is not editable by the system administrator.
However, the users, user roles, and menu access control lists, must be editable
by an administrative user. So we provide worksheets for that, eg. ZUserWorksheet,
ZUserRoleWorksheet and ZMenuAccessWorksheet.

Our "access control lists" indicate which user roles have access to a given
menu (or menu item). To keep things tidy, we have DAOs for menus, as well as
users and user roles. Our users have a many-to-many relationship to user roles,
and our user roles have a many-to-many relationship to our menus. Therefore,
we might have "membership" tables, and so DAO's for those too.

<b>public class</b> ZAccessEntityManager <b>extends</b> MEntityManager {
   <b>public</b> ZMenuInfo menu = <b>new</b> ZMenuInfo();
   <b>public</b> ZMenuMembershipInfo menuMembership = <b>new</b> ZMenuMembershipInfo();
   <b>public</b> ZUserInfo user = <b>new</b> ZUserInfo();
   <b>public</b> ZUserRoleInfo userRole = <b>new</b> ZUserRoleInfo();
   <b>public</b> ZUserRoleMembershipInfo userRoleMembership = <b>new</b> ZUserRoleMembershipInfo();
   ...
}  

where meme's MEntityManager was introduced in
Bean Curd 2: The SQL.

We might get a filtered list of those menus available for a given user role
by invoking entityManager.menuMembership.getMenuList(userRole) which might be
implemented as follows.

<b>public class</b> ZMenuMembershipInfo <b>extends</b> MEntityBean&lt;ZMenuMembership&gt; {
   ...
  
   <b>public</b> List&lt;ZMenu&gt; getMenuList(ZUserRole userRole) {
      List&lt;ZMenu&gt; menuList = <b>new</b> ArrayList();
      <b>for</b> (ZMenuMembership membership : <b>super</b>.getExtentEntityList()) {
         <b>if</b> (membership.getUserRole().equals(userRole))) {
            menuList.add(membership.getMenu());
         }
      }
      <b>return</b> menuList;
   }     
}

where MEntityBean.getExtentEntityList() gets all the entities, ie. rows in this database table.


Menu Bar Configurator

"I have feelings too - like I want beer, or I'm going crazy." Homer Simpson

When the user logs into the application, we need to build the menu bar, including
only those menus and menu items (which mostly correspond to worksheets) to
which that user has access.

We filter the list of all available menus down to the accessible menus,
and then populate the JMenuBar as follows.

    <b>public void</b> configure(JMenuBar menuBar, List&lt;ZMenu&gt; menuBeanList) {
        menuBar.removeAll();
        <b>for</b> (ZMenu parentMenuBean : getMenuBeanList(menuBeanList, <b>null</b>)) {
            JMenu topMenu = createMenu(menuBeanList, parentMenuBean);
            topMenu.setIcon(<b>null</b>);
            <b>if</b> (topMenu.getMenuComponentCount() > 0) {
                menuBar.add(topMenu);
            }
        }
        menuBar.repaint();
    }

    <b>protected</b> List&lt;ZMenu&gt; getMenuBeanList(List&lt;ZMenu&gt; menuBeanList, ZMenu parentMenuBean) {
        List&lt;ZMenu&gt; list = <b>new</b> ArrayList();
        <b>for</b> (ZMenu menuBean : menuBeanList) {
            <b>if</b> (menuBean.getParentMenu() == parentMenuBean) {
                list.add(menuBean);
            }
        }
        <b>return</b> list;
    }
   
    <b>protected</b> JMenu createMenu(List&lt;ZMenu&gt; menuBeanList, ZMenu parentMenuBean) {
        JMenu menu = createMenu(parentMenuBean);
        <b>for</b> (ZMenu menuBean : getMenuBeanList(menuBeanList, parentMenuBean)) {
            <b>if</b> (getMenuList(menuBeanList, menuBean).size() != 0) {
                menu.add(createMenu(menuBeanList, menuBean));
            } else {
                menu.add(createMenuItem(menuBean));
            }
        }
        <b>return</b> menu;
    }
   
    <b>protected</b> JMenuItem createMenuItem(ZMenu menuBean) {
        GAction action = createMenuAction(menuBean);
        JMenuItem menuItem = createMenuItem(action);
        <b>return</b> menuItem;
    }

where createMenu(ZMenu) creates a JMenu component from our ZMenu entity,
similar to the above createMenuItem(ZMenu).

We have an ordinal property on our ZMenu Jitem. If this is nonzero, then it
indicates that we wish to include this item in our tool bar, using the ordinal value
as its order in the tool bar.

    <b>public void</b> configure(JToolBar toolBar, List&lt;ZMenu&gt; menuBeanList) {
        toolBar.removeAll();
        Map&lt;Integer, ZMenu&gt; menuBeanMap = <b>new</b> TreeMap();
        <b>for</b> (ZMenu menuBean : menuBeanList) {
           <b>if</b> (menuBean.getOrdinal() != 0) {
              menuBeanMap.put(menuBean.getOrdinal(), menuBean);
           }
        }
        <b>for</b> (ZMenu menuBean : menuBeanMap.values()) {       
            GAction action = createMenuAction(menuBean);
            JButton button = createButton(action);
            button.setText(<b>null</b>);
            toolBar.add(button);
        }
        toolBar.repaint();
    }

where createMenuAction() creates a Swing Action using the menu configuration.


Worksheet Cookie Puncher


"Beer needs sports, and sports needs beer - it has always been thus." Peter Richmond

We introduce an interface for worksheets, so that our framewarez can juggle them.

<b>public interface</b> ZWorksheet {
    <b>public void</b> openWorksheet();
    <b>public</b> <b>boolean</b> closeWorksheet();
    <b>public</b> String getWorksheetLabel();
    <b>public</b> GPanel getWorksheetPanel();
    <b>public</b> String getWorksheetHelp();   
}      

The openWorksheet() method might request focus for some component,
so we will invoke it after the worksheet tab is realised, eg. using SwingUtilities.invokeLater().

beer5_250b.jpg align="left" hspace="16" vspace="4" />
The closeWorksheet() method confirms that the worksheet can be closed, eg.
asks if the user wishes to save changes, if there are any.

When the framewarez opens the worksheet, it gets the label for the tab using getWorksheetLabel(),
and the content JPanel of the worksheet using getWorksheetPanel().

If the user selects the help menu item, our framewarez might get the help
for the currently active worksheet using getWorksheetHelp().
This might be the actual HTML text to display, or it might be a bookmark into the help manual,
which makes more sense.


One of Many Worksheets

"Work is the curse of the drinking class." Oscar Wilde

We make all of our worksheets implement the above interface. Let's consider our
ZProductWorksheet. In MVC-speak, this mashes our MVC three-ball into one class.
Additionally, we reference POJO model objects, ie. instances of ZProductBean,
to complete the picture.

It would be better to split this class up, eg. put the table into ZProductTable
and the form into ZProductForm. But we aint gonna worry about that right now.

<b>public class</b> ZProductWorksheet <b>implements</b> ZWorksheet, GActionListener, GFieldListener {
   
    @WorksheetContextAnnotation()
    ZWorksheetContext context = ZWorksheetContext.createWorksheetContext(<b>this</b>);
       
    @WorksheetConfigurationAnnotation()
    ZProductWorksheetConfiguration configuration = <b>new</b> ZProductWorksheetConfiguration();
   
    @ComponentAnnotation(label = "Products")
    GPanel worksheetPanel = context.createPanel();
   
    @LayoutAnnotation(gridy = 0)
    GToolBar worksheetToolBar = context.createToolBar(worksheetPanel);
   
    @ComponentAnnotation(label = "Close worksheet")
    GAction closeAction = context.createAction(worksheetToolBar);
   
    <i>... // more actions eg. new, find, save, undo</i>
   
    @LayoutAnnotation(gridy = 1)
    GTabbedPane tabbedPane = context.createTopTabbedPane(worksheetPanel);
   
    @ComponentAnnotation(label = "Products")
    GTabPanel tableTabPanel = context.createTabPanel(tabbedPane);
   
    @ComponentAnnotation(label = "Details")
    GTabPanel formTabPanel = context.createTabPanel(tabbedPane);

    @ComponentAnnotation()
    GTable&lt;ZProductBean&gt; productForm = context.createTable(tableTabPanel, ZProductBean.<b>class</b>);
   
    @ComponentAnnotation(label = "Product Id", width = 100)
    GField productIdField = context.createField(productForm);

    <i>... // other columns</i>

    @LayoutAnnotation(spacer = Gbc.BOTH, flow = Gbc.HORIZONTAL)
    GForm&lt;ZProductBean&gt; productForm = context.createForm(formTabPanel, ZProductBean.<b>class</b>);

    @ComponentAnnotation(label = "Product Id", width = 100)
    GTextField productIdColumn = context.createTextField(productForm);
   
    <i>...// other fields</i>
   
    List&lt;ZProductBean&gt; productBeanList = <b>new</b> ArrayList();
       
    <b>public</b> ZProductWorksheet() {       
        context.configure(<b>this</b>);       
        productTable.setDataList(context.entityManager.product.getExtentEntityList());
        productForm.setBean(productTable.getBeanList().get(0));
    }
   
    <b>public void</b> openWorksheet() {
       productTable.requestFocusInWindow();
    }

    <b>public boolean</b> closeWorksheet() {
       <b>if</b> (productForm.isChanged()) {
          <b>if</b> (!context.showConfirmDialog(configuration.confirmCloseWithoutSave)) {
             <b>return false</b>;
          }
       }
       <b>return true</b>;
    }
       
    <b>public</b> GToolBar getWorksheetToolBar() {
        <b>return</b> worksheetToolBar;
    }
   
    <b>public</b> GPanel getWorksheetPanel() {
        <b>return</b> worksheetPanel;
    }
   
    <b>public</b> String getWorksheetHelp() {
        <b>return</b> configuration.worksheetHelp;
    }
   
    <b>public</b> String getWorksheetLabel() {
        <b>return</b> configuration.worksheetLabel;
    }
   
    <b>public</b> <b>void</b> actionPerformed(GActionEvent event) {
        context.traceLogger.entering(event);
        <b>if</b> (event.getAction().equals(newAction)) {
           ...
        } else <b>if</b> (event.getAction().equals(findAction)) {
           ...
        } else <b>if</b> (event.getAction().equals(saveAction)) {
            ...
        } else <b>if</b> (event.getAction().equals(closeAction)) {
            context.closeWorksheet(<b>this</b>);
        } else <b>if</b> (event.getAction() == helpAction) {
            helpActionPerformed();
        } else {
            context.traceLogger.warning(event);
        }
        setEnabled();
    }

    <b>public</b> <b>void</b> helpActionPerformed() {
        context.framewarez.showHelp(getWorksheetHelp());
    }
   
    <b>public void</b> setEnabled() {
       saveAction.setEnabled(productForm.isChanged());
       undoAction.setEnabled(productForm.isChanged());
    }   
   
    ...
}   

As you can see, we annotate everything, even if we dunno why we would want to (yet). Mmmm, Duff, mmmm, annotations...

Note that we use our tabbed framewarez to display our help as a new tab in helpActionPerformed().
In general, we favour opening a tab rather than popping up a JDialog. For one thing,
this allows the user to switch between tabs to remind themselves what they are
doing. If we really need a modal dialog, then we will use one, but otherwise we go for tabs.


Worksheet Configuration Class

"Beer, the cause and solution to all of life's problems." Homer Simpson

We distill translatable strings into a worksheet configuration class as follows.

<b>public class</b> ZProductWorksheetConfiguration {

    String worksheetLabel = "Products";       
   
    String worksheetHelp = 
        "&lt;b&gt;Product catalogue maintenance worksheet&lt;/b&gt; \n\n" +
        "Use this worksheet to maintain our catalogue of beer products.";   
       
    String confirmCloseWithoutSave = "Close without saving changes?";
   
    <i>... // other messages, eg. exception messages, dialog messages</i>
}   

This class can be externalised to an XML file for customisation and translation.
In addition to the above strings, our configuration bundle implicitly includes the
configuration of our components, to override the defaults specified in the annotations,
notably the labels and tooltips, eg. the tooltip for closeAction,
tab label for tableTabPanel, column label for productIdColumn, et cetera.

The configuration might be generated for a resource bundle as follows.

    productWorksheet.message.worksheetLabel = Products
    productWorksheet.message.confirmCloseWithoutSave = Close without saving changes?
    productWorksheet.message.worksheetHelp = &lt;b&gt;Product catalogue maintenance worksheet&lt;/b&gt; ...
    productWorksheet.panel.worksheetPanel.label = Products
    productWorksheet.action.closeAction.toolTip = Close worksheet
    productWorksheet.tab.tableTabPanel.label = Products
    productWorksheet.tab.formTabPanel.label = Details
    productWorksheet.field.productIdField.label = Product Id
    productWorksheet.column.productIdColumn.label = Product Id

Alternatively, its XML representation might look like the following.

    &lt;worksheetConfiguration name="productWorksheet"&gt;
        &lt;message name="worksheetLabel" value="Products"/&gt;
        &lt;message name="confirmCloseWithoutSave" value="Close without saving changes?"/&gt;
        &lt;message name="worksheetHelp"&gt;
            &lt;b&gt;Product catalogue maintenance worksheet&lt;/b&gt;
            Use this worksheet to maintain our catalogue of beer products.         
        &lt;/message&gt;
        &lt;panel name="worksheetPanel" label="Products"/&gt;
        &lt;action name="closeAction" toolTip="Close worksheet"/&gt;
        &lt;tab name="tableTabPanel" label="Products"/&gt;
        &lt;tab name="formTabPanel" label="Details"/&gt;
        &lt;field name="productIdField" label="Product Id"/&gt;
        &lt;column name="productIdColumn" label="Product Id"/&gt;
    &lt;/worksheetConfiguration&gt;

In our ZWorksheetContext.configure() method,
we would parse the above into a list of MessageConfiguration,
ActionConfiguration, PanelConfiguration, TabConfiguration,
FieldConfiguration and ColumnConfiguration objects. And then
apply these configurations to our ZProductWorksheetConfiguration instance,
and the components of our ZProductWorksheet, to override the defaults
eg. as specified in annotations.


Framewarez

"Without hydrogen and oxygen, there would be no water, a vital ingredient in beer." Dave Barry.

Our framewarez is implemented as follows. For starters, we show a login tab. Once the
user has logged in, we configure the menu bar appropriately according to the user's
access permissions. Then the user can start launching worksheets.

login.png

<b>public class</b> ZAccessFrame <b>implements</b> GFieldListener, ActionListener, GTableListener {
   
    @WorksheetContextAnnotation()
    ZWorksheetContext context = ZWorksheetContext.createWorksheetContext(<b>this</b>);
   
    @WorksheetConfigurationAnnotation()
    ZAccessFrameConfiguration configuration = <b>new</b> ZAccessFrameConfiguration();
   
    @LayoutAnnotation()
    GFrame mainFrame = context.createFrame();
   
    @LayoutAnnotation()
    GMenuBar mainMenuBar = context.createMenuBar(mainFrame);
   
    @LayoutAnnotation(gridy = 0)
    GToolBar mainToolBar = context.createToolBar();
   
    @LayoutAnnotation(gridy = 1)
    GTabbedPane mainTabbedPane = context.createBottomTabbedPane(mainPanel);
   
    @LayoutAnnotation()
    GPanel textPanel = context.createPanel();
   
    @LayoutAnnotation(top = 20, gridy = 2)
    GTextPane textPane = context.createTextPane(textPanel);
   
    @LayoutAnnotation()
    @ComponentAnnotation(label = "Login")
    GPanel loginPanel = context.createPanel();
   
    @LayoutAnnotation(gridy = 1)
    GForm loginForm = context.createForm(loginPanel, ZLoginBean.<b>class</b>);
   
    @LayoutAnnotation(gridx = 0, width = 100)
    @ComponentAnnotation(label = "Username")
    GTextField usernameField = context.createTextField(loginForm);
   
    @LayoutAnnotation(gridx = 1, width = 100)
    @ComponentAnnotation(label = "Password")
    GPasswordField passwordField = context.createPasswordField(loginForm);

    @LayoutAnnotation(gridx = 2)
    GAction loginAction = context.createButton(loginForm);
       
    ZLoginBean loginBean = <b>new</b> ZLoginBean(<b>this</b>);
   
   
    <b>public</b> ZAccessFrame() {
        context.configure(<b>this</b>);
        loginForm.setBean(loginBean);
        setEnabled();
    }

    <b>public void</b> fieldChanged(GFieldEvent event) {
        context.fieldLogger.entering(event);
        <b>if</b> (event.getField() == usernameField) {
            usernameChanged();
        } <b>else if</b> (event.getField() == passwordField) {
            passwordChanged();
        }
        setEnabled();
    }
       
    <b>protected</b> <b>void</b> usernameChanged() {
        loginBean.validateUsername();
        passwordField.setEnabled(<b>true</b>);
        passwordField.requestFocusInWindow();
    }
   
    <b>protected</b> <b>void</b> passwordChanged() {
        loginForm.getBean();
        loginBean.validate();
        loginAction.setEnabled(<b>true</b>);
        loginAction.requestFocusInWindow();
    }
   
    <b>protected</b> <b>void</b> setEnabled() {
        loginAction.setEnabled(loginBean.validate());
    }
   
    <b>public void</b> actionPerformed(ActionEvent event) {
        ZMenu menu = context.entityManager.menu.getNullableEntityBean(event.getActionCommand());
        context.actionLogger.entering(event, menu);
        <b>if</b> (menu != <b>null</b> && menu.getWorksheetClass() != <b>null</b>) {
            openWorksheet(menu.getWorksheetClass());
        } <b>else if</b> (loginAction.isSource(event)) {
            loginActionPerformed();
        } <b>else if</b> (context.accessData.systemLogout.isSource(event)) {
            logoutActionPerformed();
        } <b>else if</b> (context.accessData.systemExit.isSource(event)) {
            exitActionPerformed();
        } <b>else if</b> (context.accessData.helpOnlineHelp.isSource(event)) {
            helpActionPerformed();
        } <b>else if</b> (context.accessData.helpAbout.isSource(event)) {
            aboutActionPerformed();
        } else {
            context.traceLogger.warning(event);
        }
        setEnabled();
    }
       
    <b>public void</b> loginActionPerformed() {
        loginForm.getBean();
        loginBean.validate();
        loginUser();
    }
   
    <b>protected</b> <b>void</b> loginUser() {
        context.setUser(loginBean.getUser());
        showMenu();
    }

    <b>protected</b> <b>void</b> showMenu() {
        context.configure(mainMenuBar, context.getUser());
        context.configure(mainToolBar, context.getUser());
        mainMenuBar.requestFocusInWindow();
    }
       
    <b>protected</b> <b>void</b> logoutActionPerformed() {
        context.setUser(<b>null</b>);
        loginBean = <b>new</b> ZLoginBean(<b>this</b>);
        loginForm.setBean(loginBean);
        mainTabbedPane.removeAll();
        mainTabbedPane.addTab(loginPanel);
        username.requestFocusInWindow();
    }
   
    <b>protected</b> <b>void</b> openWorksheet(Class worksheetClass) {
        <b>try</b> {
            ZWorksheet worksheet = (ZWorksheet) worksheetClass.newInstance();
            openWorksheet(worksheet);
        } <b>catch</b> (Exception e) {
            <b>throw new</b> GWrappedRuntimeException(context, e, configuration.openWorksheetError, worksheetClass);
        }
    }
   
    <b>protected</b> <b>void</b> openWorksheet(final ZWorksheet worksheet) {
        String tabName = worksheet.getWorksheetLabel();
        <b>int</b> index = mainTabbedPane.indexOfTab(tabName);
        <b>if</b> (mainTabbedPane.indexOfTab(tabName) >= 0) {
            mainTabbedPane.setSelectedIndex(mainTabbedPane.indexOfTab(tabName));
            <b>return</b>;
        }
        JPanel worksheetPanel = worksheet.getWorksheetPanel();
        mainTabbedPane.addTab(tabName, worksheetPanel);
        mainTabbedPane.setSelectedComponent(worksheetPanel);
        worksheet.openWorksheet();
    }
   
    <b>public void</b> closeWorksheet(ZWorksheet worksheet) {
        <b>if</b> (worksheet.closeWorksheet()) {
            mainTabbedPane.remove(worksheet.getWorksheetPanel());
        }
    }
   
    ...   
}
   

Not rocket science, just framewarez, so...

A handy trick is to enable automatic login, eg. via -DautoLoginUser=test.
Then in development mode, we can press F6 to compile and run the framewarez, and
straight-away click on the toolbar icon for the worksheet we are working on, to preview and test it in a tight
loop without the niggle of logging in a million times a day. For this reason if nothing else, we need to
minimise our application startup time, eg. by using lazy initialisation, and also not overriding our default
configuration from externalised configuration files and preferences, eg. via -DsuppressExternalisedConfiguration=true


Conclusion

"The answers aren't at the bottom of a beer bottle, they're on TV!"
Homer Simpson.

We present a design of a JFrame with a JTabbedPane for worksheets. We install a JMenuBar
on the JFrame for launching worksheets. Menu items are configured in the application, and overridden
with the externalised configuration, eg. using JAXB2 to persist the configuration to an XML file. This enables
translation and customisation.

leffe.jpg align=right vspace=4 hspace=16 border=0 />
Access control to the menu items is based on user roles. This is persisted to the database, and maintained
by the administrative user using worksheets, ie. for users and user roles, and the corresponding
menu access control lists.


This design is implemented in aptframework.dev.java.net,
minus some refinements presented here. If you wish to dive deeper into some undocumented non-sugar-coated code, you can look there.

Here's that Web Start demo again...

webstart.small.gif
(695k, unsandboxed, Java5)


Coming up


"I can resist everything except temptation." Oscar Wilde


The next article in this Swing and Roundabouts series might be Lookup Hookup looking at
handling database lookups for fields and columns, using this framewarez.

Before that, two upcoming articles in the works are
Trip and Tick 2: JooJ up your project page with a Web Start demo
and Plumber's Hack 1: Blog o' warez about some softwarez i'm writing to help me write
technical articles like this.

Before that, you can read Trip and Tick 1: Checking out a java.net project
and The sharp end of the stick with some discussion
about languages, platforms, and what-not.

Related Topics >>