Workshop Controls and Extensions: Part 1 - Writing an Extension

by Emmanuel Proulx and Lucian Agapie
01/31/2005

Abstract

WebLogic Workshop 8.1 offers a world of possibilities to those who know how to extend it. This article gives a real-world example of how to extend Workshop by adding a menu item, a toolbar button, and a frame. It is the first in a series examining Workshop controls and extensions. Future articles will look at the help system, wizards, distribution, and other topics.

Introduction

We're in an age of convergence. Convergence occurs when technologies merge. When BEA brought us WebLogic Workshop 8.1, it merged the best features of J2EE, web services, and the WebLogic Platform technologies. At the same time, BEA opened the door for merging custom technologies into Workshop. This is done using controls and extensions.

Workshop encompasses a highly customizable user interface, which includes the development environment, classes, runtime environment, and so on. In this series we will explore some of these features. We will do so by developing a set of controls and extensions called "plug-ins" by other IDE vendors. By the end of this series you should be able to quite comfortably build arbitrary extensions to the WebLogic Workshop environment.

Case Study

Before we start writing our controls and extensions, we need a case study. Sometimes we want to collaborate with remote developers. Let's assume that they don't have access to a source control repository. A good way to send them your source code is through email. But the process of opening your email client, attaching a Java file, and sending it can take a few minutes. What if you could send the document directly from inside Workshop?

In this article we are going to create a simple email client, which will be activated by a button or, alternatively, by a menu item.

By completing this project, you will learn how to:

  • create, manage, build, and deploy an extension project
  • create a menu item, a toolbar button, and a view in Workshop
  • invoke an extension programmatically

Extension Points

Extension points are a mechanism that determines the interface between the main Workshop program and extensions. It consists of a set of predefined places that allow the inclusion of arbitrary new objects, such as menus, toolbars, buttons, and views, on the main user interface.

The Workshop core comes with a set of extension points. Custom extensions can use these extension points to add their own features. These custom extensions can also add their own extension points for other extensions to use. This creates a hierarchy of extension points.

Extension points are retrieved by name (text value). They can hold objects that are of various types, depending on the kind of extension point. This means all extension points must be documented in order to be usable. A list of basic extension points is available in the Workshop help system. To view this list, go to the menu Help | Help Topics, then navigate to the category:

Extension Development Kit -> Extending the WebLogic Workshop IDE -> Extension XML Reference

When an extension wishes to add itself to Workshop, it must do so in one of two ways:

  1. Declaratively listing the objects to add to extension points through the deployment descriptor extension.xml
  2. Programmatically adding these objects; this must be triggered somehow, so usually an initial object is added in extension.xml

There will be more coverage of the extension.xml file later on.

Structure of an Extension Project

Typically, an extension is a Workshop project with the structure shown in Figure 1. Our project is going to follow the same outline.

Project Structure
Figure 1. Project structure

The following table describes these folders and files.

Item Description Folder
controls_extension Application hosting one or more extensions and controls /controls_extensions
my_email Project hosting a single extension or control /controls_extensions/my_email
META-INF Archive meta-information folder /controls_extensions/my_email/META-INF
extension.xml Deployment descriptor, contains the extension-point/object mappings /controls_extensions/my_email/META-INF/extension.xml
MANIFEST.MF Archive's manifest file. Contains the classpath needed by the extension at runtime /controls_extensions/my_email/META-INF/MANIFEST.MF
src Base folder for all project code files /controls_extensions/my_email/src
dev2dev/controls_extensions Packages of the source files /controls_extensions/my_email/src/dev2dev/controls_extensions
images Some extension objects (toolbar buttons, menus) may contain graphics; this folder hosts those graphics /controls_extensions/my_email/src/images
email.gif Image used for the email menu item and toolbar button /controls_extensions/my_email/src/images/email.gif
build.xml Ant build script; creates the extension archive file /controls_extensions/my_email/build.xml
Libraries Contains the list of supporting libraries, which aren't in the classpath of Workshop but are needed for compilation and running /controls_extensions/APP-INF/lib

Other folders aren't relevant for us. Notice that the last item isn't named like the folder it represents.

Figure 2 shows the resulting JAR file after the extension is compiled and archived.

Java Archive Structure
Figure 2. Java archive structure

In the JAR file you will find the compiled files in their packages and other support files as they appear in the project.

Extending Workshop

The rest of this section provides a step-by-step guide to creating the email project. The complete source code is also available for download at the end of the article.

We will describe adding the extension by looking at several phases of development: creating the project structure, creating the build support files, creating the extension files, building the extension files, creating the support file, and, finally, implementing the extension.

Setting up our project

In this phase we will show you how to create a Workshop project for the extension and set up the classpaths and the integrated debugger.

1. First, we are going to create a new application. Select File | New | Application from the main menu. In the New Application dialog box choose an Empty Application from the All category. Type "dev2dev" in the Directory field and "controls_extensions" in the Name field. Leave the sample Workshop server, which appears as default in the Server field, and then click on the Create button.

A directory called controls_extensions and subdirectories called Modules, Libraries, and Security Roles will be created and will be visible in the Application pane. This is the application that will host our extension. A single application can contain many projects like extensions and controls.

2. Add a new project for the extension. Right-click on the folder named controls_extensions in the Application pane, and then select File | New | Project. In the dialog box called New Project select the Java Project in the Business Logic category. Type "my_email" in the Project Name field, and press the Create button. A new folder called my_email will appear in the Application pane under controls_extensions.

This project represents a single extension. It will be built into a deployable extension JAR file.

3. Create a repository for the source files. Right-click my_email, select New|Folder, type "src" in the Create New Folder dialog box, and then press OK. This is where all Java files will be saved under their respective packages. Only this folder will be compiled. Other folders contain supporting files.

4. Set the properties for the project. On the Application pane right-click on the project my_email, and select Properties from the menu. The project properties dialog box will appear. We will now set many things inside this dialog.

Note: The provided source code may not compile if these properties aren't set.

a) Select the Paths category.

  • Classpath: Workshop extensions need a special library in order to be compiled and executed. This is the wlw-ide.jar library, and it contains all extension classes and interfaces. It is in the classpath when Workshop starts, but it is not in the classpath when compiling an extension. We need to add it. Click on the Add Jar button beside the Classpath list on the right side of the window, and browse to find: BEA_HOME\weblogic81\workshop\wlw-ide.jar, where BEA_HOME is the folder in which the BEA WebLogic is installed. This ensures the classes needed to build the application are provided.

  • Source path: We don't have to compile everything in our project. Only the src folder contains Java code. We can specify this here. Click on Add Path beside the Source Path list on the right side of the window. Browse to find the C:\bea\user_projects\applications\controls_extensions\src folder, and click Select Directory.

b) To compile our extension, we can rely on the default Workshop build mechanism. But this doesn't allow us to automatically deploy our extension. The only way we can do this is using an Ant build script. Workshop can create a default script that is customizable. Select Build on the left side of the window.

On the right side under Build Type click on "Export to ant file" under "Use IDE build," and then press the OK button in the dialog box. A new file, exported_build.xml, has been created in the my_email folder. We will customize this file later on.

c) Workshop is a great tool for extension development because it has an integrated debugger. But before we can debug an application we must configure Workshop. Select Debugger on the left side of the window.

On the right side of the window, under Debugging Options, check "Build before debugging," uncheck "Pause all threads after stepping," and then select the radio button for "Create new process." Type "workshop.core.Workshop" in the Main Class field and enable "Smart debugging." The extension is now ready for debugging. Simply set a breakpoint in the code, and then run the debugger. A new instance of Workshop will come up, and you will be able to step into the code of your extension!

For more information regarding debugging options, see Setting Up Extension Debugging Properties.

Creating the build support

In this phase we will show you how to customize our Ant build file.

5. Build file customizations: Since "exported_build.xml" isn't a common name or even the norm, let's rename it. In the Application pane right-click on the file exported_build.xml under the my_email project folder, and select Rename from the pop-up menu. Rename it to "build.xml" in order to be recognized by Ant.

Double-click on build.xml to open it on the editor window. We are going to add the following lines at the end of the build task:

<zip destfile="${platformhome.local.directory}/workshop/extensions/${output.filename}"

    basedir="${dest.path}" includes="**/*.*" encoding="UTF8">

    <zipfileset dir="${project.local.directory}"

        excludes="build.xml,**/CVS/**,**/*.java,${output.filename}" includes="**/*.*"/>

</zip>

Those lines will create new JAR file in the extensions deployment folder, ready to be executed the next time Workshop starts.

Below this, add the following:

<copy todir="${platformhome.local.directory}/workshop/lib"

    file="${app.local.directory}/APP-INF/lib/mail.jar"/>

This command will copy the third-party mail.jar library to its deployment folder.

The last customization step is to make Workshop use our new Ant script instead of the default. Again, open the project properties, and in the Build category, click on the Use Ant Build radio button. Keep the defaults.

Creating extension.xml

At this point we have completed the preparation work. Let's dig into the coding part.

6. Workshop expects to find the controls and extensions deployment descriptors in a folder called META-INF. Let's create it. Right-click on the my_email project folder, and choose New | Folder from the pop-up menu. In the Dialog box type "META-INF," and press OK.

In this phase, we will show you how to create an extension point file and customize it to your needs. The mechanism for creating new extension points is beyond the scope of this article.

7. Add the file extension.xml. This serves to define the extension objects as explained previously. Right-click on the META-INF folder, and then choose New | Other File Types from the pop-up menu. In the New File dialog box that appears, choose "XML file" from the Common category, and name it "extension.xml." Then click the Create button. Be warned: If the file name or its contents have a typo, the extension will compile fine but will not work. There may not be any error message.

Let's write the contents of extension.xml. This file starts with the root tag <extension-definition>. Inside this there is a list of <extension-xml> tags. Each tag describes a single kind of extension point. We want to create a toolbar button and a menu item. These extensions are both added in extension points of type "action." Therefore, the following is used to specify action extensions:

<extension-xml id="urn:com-bea-ide:actions">

The "id" attribute contains a unique identifier for the kind of extension point that interests us. The syntax for the inside of the <extension-xml> tag is specified in the documentation. It is particular to each kind of extension point. For "actions," the syntax is two tags: <action-ui> and <action-set>. The first specifies the visible elements to create (menu, pop-up menu, toolbar button) and associates them to an action. The second lists those actions and the Java classes of type IAction.

Here's the complete listing:

<extension-definition>

    <extension-xml id="urn:com-bea-ide:actions">



        <action-ui>

            <menu id="email" path="menu/main" priority="100" label="E&amp;mail">

                <action-group id="messagesgroup" priority="10"/>

            </menu>



            <toolbar id="my_email" path="toolbar/main" priority="2" label="Email">

                <action-group id="default" priority="10"/>

            </toolbar>

        </action-ui>



        <action-set>

            <action class="dev2dev.controls_extensions.EmailApplication" 

            label="E&amp;mail" icon="images/email.gif" show-always="true">

                <location priority="10" path="menu/email/messagesgroup"/>

            </action>



            <action class="dev2dev.controls_extensions.EmailApplication" 

            label="Email" icon="images/email.gif" show-always="true">

                <location priority="10" path="toolbar/my_email/default" />

            </action>

        </action-set>



    </extension-xml>

</extension-definition>

A few notes on this file:

  • The relative positioning of menu items and toolbar buttons is specified by an integer "priority" attribute.

  • The path attribute serves to reference an already existing menu or toolbar. It contains a list of IDs of the parent menus/toolbars, separated by forward slashes ("/"). How do you know what IDs to use? Unfortunately, this isn't well-documented. We had to resort to looking inside the Shell extension (C :\bea\weblogic81\workshop\extensions\shell.jar), in the file extension.xml. This is where the basic menus and toolbars are created.

  • Keyboard shortcuts are represented by the "&" character (coded in XML it becomes "&amp;").

  • Images are loaded by the classloader. So they must be specified as a relative path to the root of the src folder.

  • There are two UI elements (a toolbar button and a menu item), but there's only one action. Both UI elements execute the same action.

Creating the support files

In this phase we will show you how to create the support files for the project.

8. Create the file MANIFEST.MF in the META-INF folder. This tells Workshop where to look for any third-party libraries. In our case we need the JavaMail API library file. This is the content of MANIFEST.MF:

Class-Path: ../lib/mail.jar

The destination folder for the extension JAR file is C:\bea\weblogic81\workshop\extensions. So one level up and into the lib folder means the file mail.jar is located in the folder C:\bea\weblogic81\workshop\lib.

9. The graphic files are to be put in the images subfolder inside src, as we just discussed. Graphics for toolbar buttons and menu items are 16x16 pixel GIF files, with transparent color set for the background. We created the file email.gif, so you don't have to draw one yourself; just save this icon to your computer: email.gif

Implementing the extension

In a previous section we defined the class to be executed for an action in the class attribute of the <action> element. Now we will implementing this class.

10. Let's code the action class. Create the file: /dev2dev/controls_extensions/EmailApplication.java. This class must implement the IAction interface. A convenient way to do this is to subclass the DefaultAction. This way only the actionPerformed() method has to be implemented. This method is called by Workshop when the action is executed. In our case this method opens a new frame to enter the email information. We will create this frame later. For now just type in the following listing:

package dev2dev.controls_extensions;



import com.bea.ide.actions.DefaultAction;

import com.bea.ide.ui.frame.FrameSvc;

import java.awt.event.ActionEvent;

import javax.swing.JOptionPane;

import net.sourceforge.jiu.util.Statistics;



public class EmailApplication extends DefaultAction {

    public void actionPerformed(ActionEvent e) {

        FrameSvc.LayoutConstraints layout = new FrameSvc.LayoutConstraints();

        layout.askAvailable = false;

        layout.exact = false;

        layout.focus = true;

        layout.hasAction = false;

        layout.hasMenu = false;

        layout.icon = null;

        layout.insert = true;

        layout.label = "eMail";

        layout.open = true;

        layout.orientation = FrameSvc.NORTH;

        layout.proportion = 0.20;

        layout.scope = null;

        layout.viewClassDest = null;

        layout.viewIdDest = "main";

        layout.visible = true;



        FrameSvc.get().addView("dev2dev.controls_extensions.EmailFrame", layout);

    }

}

This piece of code is an example of how one can programmatically instantiate an extension. One could also display this same pane by adding a frame extension in extension.xml.

Workshop contains many classes that allow manipulating its windowing system and internal state. The FrameSvc is a helper class to create new panes in the Workshop GUI. Here we summon the EmailFrame class by name, and we specify its placement using a LayoutConstraints object. Refer to Workshop's documentation for help with these classes. This is available in the menu Help | Help topics, then in the category WebLogic Workshop Reference, Workshop API Javadoc Reference, Extension API Reference. You will have hours of fun browsing the many classes here.

11. The class EmailFrame doesn't exist yet, so addView() will fail in the previous listing. Let's add the missing class. Create the file: /dev2dev/controls_extensions/ EmailFrame.java.

A Frame is a class that implements the IFrameView interface. This interface's two methods are:

  • isAvailable(): Indicates (true or false) if a Workshop is in a state that allows displaying the frame. We could forbid displaying our frame in certain situations if we wanted to.

  • getView(): Returns a visual Component to display as the frame. Usually this is a JPanel.

package dev2dev.controls_extensions;



import java.awt.Component;

import java.awt.FlowLayout;

import java.util.Properties;



import javax.mail.Address;

import javax.mail.Message;

import javax.mail.Session;

import javax.mail.Transport;

import javax.mail.internet.InternetAddress;

import javax.mail.internet.MimeMessage;

import javax.swing.JButton;

import javax.swing.JLabel;

import javax.swing.JOptionPane;

import javax.swing.JPanel;

import javax.swing.JTextField;



import com.bea.ide.Application;

import com.bea.ide.document.IDocument;

import com.bea.ide.ui.frame.IFrameView;



import com.bea.ide.util.IOUtil;



public class EmailFrame extends JPanel implements IFrameView {

    JTextField toField;

    JTextField subjectField;

    JButton sendBtn;

    Session mailSession;



    public EmailFrame() {

        this.initComponents();

    }



    public Component getView(String arg0) {

        return this;

    }



    public boolean isAvailable() {

        return true;

    }



    private void initComponents() {

        this.setLayout(new FlowLayout());

        sendBtn = new javax.swing.JButton();

        sendBtn.setText("Send");

        sendBtn.addActionListener(new java.awt.event.ActionListener() {

            public void actionPerformed(java.awt.event.ActionEvent event) {

                Session session = getMailSession();

                MimeMessage msg = new MimeMessage(session);



                try {



                String messageText;



                IDocument doc = Application.getActiveDocument();

                messageText = IOUtil.read(doc.getURI());

                msg.setText(messageText);



                Address fromAddr = new InternetAddress(

                    "sender@exemaple.com",

                    "Joe Doe");



                msg.setFrom(fromAddr);

                Address toAddr = new InternetAddress(toField.getText(), null);

                msg.addRecipient(Message.RecipientType.TO, toAddr);



                msg.setSubject(subjectField.getText());



                Transport.send(msg);



                } catch (Throwable e) {



                    JOptionPane.showMessageDialog(null, e.toString(),

                        "Error while sending mail!",

                        JOptionPane.ERROR_MESSAGE);

                 }



            }

        });

        JLabel toLabel = new JLabel("To:");

        toField = new JTextField(40);

        JLabel subjectLabel = new JLabel("Subject:");

        subjectField = new JTextField(40);

        add(toLabel);





        add(toField);

        add(subjectLabel);

        add(subjectField);

        add(sendBtn);

    }



    protected Session getMailSession()

    {

        if (mailSession == null) {

            Properties p = new Properties();

            p.put("mail.host", "mailServerAddress");

            p.put("mail.user", "userName");

            p.put("mail.password", "passwod");



            mailSession = Session.getDefaultInstance(p,  null);

        }

        return mailSession;

    }

}

In short, we have here a JPanel that contains a To field (destination address), a Subject field, and a Send button. Inside the Send button, we use the JavaMail API to send an email with the document opened in the editor.

Three interesting classes are used here. One is Application, which is used to retrieve the active (opened) document. The second is IDocument, an interface that represents that document. This has a getURI() method that returns the location of the file. Finally, there's the very cool IOUtil, used here to copy the file into a String. These are all documented in the Extension API Reference as noted above.

The Result

After the project has been built, select Debug | Start from the main menu. Note the email icon on the left side of the toolbar as well as the new item named Email in the main menu. If you click on the icon a new frame is going to appear along the top side with the To and Subject fields and the Send button, as you can see in Figure 3.

Result Project
Figure 3. Result of the project

Conclusion

With Workshop's powerful, easy-to-use extension mechanism, convergence is achievable. Whatever technology you use in your company, you can now easily integrate it into Workshop as an extension or a control. As you can see, there are many more tools to investigate in Workshop, so there are countless applications one can develop.

In the next article we will explore the Workshop help system, and we will supplement a control with documentation suitable for official validation (validated controls and extensions can be added to the Premier Component Gallery).

Emmanuel Proulx is an expert in J2EE and SIP. He is a certified WebLogic Server engineer.

Lucian Agapie is a senior electrical engineer, member of IEEE, who enjoys writing software applications in Java, C++ and TCL/TK.