Skip to main content

NetBeans plug-in for AVK - Implementation details of the AVK plug-in

Posted by bhavanishankar on June 5, 2006 at 11:54 PM PDT

My previous blog entry explained about AVK and how to use AVK from within NetBeans.



I thought it is worthwhile spending some time in writing down the detailed description of the tasks involved in writing this plug-in. This article is intended for the NetBeans plug-in developers.

 

Implementation details of AVK plug-in :



This is what the AVK plug-in does internally:



1. Adds "Dynamic Verification" action to "Verify Project" task.



2. When "Dynamic Verification" action is invoked then the action handler



    A. Configures application server to run in AVK mode (by invoking MBeans deployed on the application server through JMX APIs).

    B. Deploys the application (by invoking "run-deploy" ANT task)

    C. Lauches the application in the browser if the application has web component.

    D. Invokes the AVK tool (by creating a seperate process)

    E. Launches "AVK Session" window showing the dynamic verification results.



3. Source code linking from the swing UI : From the "AVK Session" window - Right click "Servlet Name" or "Method Name" > "Go To Source" to view the appropriate source code of your application.
In the following sections I will be describing each of the implementation details outlined above. I am explaining the implementation details in general not being specific to AVK plug-in.


    Step 0. Creating a new NetBeans plug-in project




File > New Project > NetBeans Plug-in Modules > Module Project (Choose Project name[=AVK plug-in] and code base name[=avkplugin] and leave the rest at default values).

    Step 1. Adding an action to the Projects menu



Project > Right Click > New > Action (Select Category=Tools, Menu=Tools, Position=Update Center - HERE, Classname=MyAction).



Open layer.xml and change


<folder name="Menu"> to <folder name="Projects">
<folder name="Tools"> to <folder name="Tools">



In my case, layer.xml looks like this:


<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE filesystem PUBLIC "-//NetBeans//DTD Filesystem 1.1//EN" "http://www.netbeans.org/dtds/filesystem-1_1.dtd">
<filesystem>
    <folder name="Actions">
        <folder name="Tools">
            <file name="avkplugin-MyAction.instance"/>
        </folder>
    </folder>
    <folder name="Projects">
        <folder name="Actions">
            <attr name="org-netbeans-modules-autoupdate-UpdateAction.instance/avkplugin-MyAction.shadow" boolvalue="true"/>
            <file name="avkplugin-MyAction.shadow">
                <attr name="originalFile" stringvalue="Actions/Tools/avkplugin-MyAction.instance"/>
            </file>
        </folder>
    </folder>
</filesystem>





Change MyAction.java to inherit from NodeAction instead of from CallableSystemAction.  Implement performAction(...) method. In my case I simply print 'My action is invoked' message. Here is my MyAction.java


package avkplugin;

import org.openide.nodes.Node;
import org.openide.nodes.NodeAcceptor;
import org.openide.util.HelpCtx;
import org.openide.util.NbBundle;
import org.openide.util.actions.NodeAction;

public final class MyAction extends NodeAction {
    
    public void performAction(Node[] nodes) {
        System.out.println("My action is invoked...");
    }
    
    public String getName() {
        return NbBundle.getMessage(MyAction.class, "CTL_MyAction");
    }
    
    protected void initialize() {
        super.initialize();
        // see org.openide.util.actions.SystemAction.iconResource() javadoc for more details
        putValue("noIconInMenu", Boolean.TRUE);
    }
    
    public HelpCtx getHelpCtx() {
        return HelpCtx.DEFAULT_HELP;
    }
    
    protected boolean asynchronous() {
        return false;
    }

    protected boolean enable(Node[] node) {
        return true;
    }
    
}



At this point, my project uses the following libraries:


Nodes APIs

Utilities APIs



To install the plug-in : Project > Right Click > Install/Reload in Development IDE.



After the plug-in is successfully installed "My Action" action gets added to the Project. To invoke the action : Project > Right Click > My Action. You will see "My action is invoked" on the console from where you launched the IDE.



    Step 2A: Invoking MBeans deployed on the application server using JMX APIs:



Let us take an example of reading the deployment directory of a JavaEE application from the domain.xml


package avkplugin;

import java.util.HashMap;
import java.util.Map;
import javax.management.MBeanServerConnection;
import javax.management.ObjectName;
import javax.management.remote.JMXConnector;
import javax.management.remote.JMXConnectorFactory;
import javax.management.remote.JMXServiceURL;
import org.netbeans.api.project.Project;
import org.netbeans.modules.j2ee.deployment.devmodules.spi.J2eeModuleProvider;
import org.netbeans.modules.j2ee.deployment.plugins.api.InstanceProperties;
import org.openide.nodes.Node;
import org.openide.util.HelpCtx;
import org.openide.util.Lookup;
import org.openide.util.NbBundle;
import org.openide.util.actions.NodeAction;

public final class MyAction extends NodeAction {
    
    public void performAction(Node[] nodes) {
        System.out.println("Deployment directory = " + getDeployDir(getProject(nodes[0])));
    }
    
    public Project getProject(Node projectNode) {
        Lookup lookup = projectNode.getLookup();
        Project project = (Project)lookup.lookup(Project.class);
        return project;
    }
    
    public String getDeployDir(Project project) {
        
        Lookup projectLookup = project.getLookup();
        J2eeModuleProvider moduleProvider = (J2eeModuleProvider)projectLookup.lookup(J2eeModuleProvider.class);
        
        String moduleName = moduleProvider.getDeploymentName().toLowerCase();
        String objectName = "com.sun.appserv:type=j2ee-application,name=" + moduleName + ",category=config";
        
        InstanceProperties instProps = moduleProvider.getInstanceProperties();
        
        String deployDir = null;
        
        String adminUser = instProps.getProperty("username");
        String adminPassword = instProps.getProperty("password");
        String adminPort = instProps.getProperty("httpportnumber");
        String jmxUrl = "service:jmx:rmi:///jndi/rmi://localhost:8686/jmxrmi"; // hardcoded.
        
        String[] credentials = new String[] { adminUser, adminPassword };
        Map env = new HashMap();
        env.put("jmx.remote.credentials", credentials);
        try {
            JMXServiceURL url = new JMXServiceURL(jmxUrl);
            JMXConnector jmxc = JMXConnectorFactory.connect(url, env);
            MBeanServerConnection mbsc = jmxc.getMBeanServerConnection();
            deployDir = (String)mbsc.getAttribute(new ObjectName(objectName), "location");
        } catch(Exception ex) {
            ex.printStackTrace();
        }
        return deployDir;
    }
    
    .....

}



At this point, my project uses the following additional libraries:

J2EE Server Registry

Project API



    Step 2B: Deploying the application by invoking "run-deploy" ANT task



This is how one can invoke any build target from within an action


.....

import org.apache.tools.ant.module.api.support.ActionUtils;

import org.netbeans.spi.project.support.ant.GeneratedFilesHelper;

import org.openide.filesystems.FileObject;

.....



public final class MyAction extends NodeAction {



    public void performAction(Node[] nodes) {

        runBuildTarget(getProject(nodes[0]), "run-deploy");

    }



    .....



    public void runBuildTarget(Project project, String targetName) {

        FileObject buildXML = project.getProjectDirectory().getFileObject(GeneratedFilesHelper.BUILD_XML_PATH);

        try {

            ActionUtils.runTarget(buildXML, new String[]{targetName}, new Properties());

        } catch (Exception ex) {

            ex.printStackTrace();

        }

    }



    .....

}



Additional libraries needed for this purpose


File System API

Ant

Ant-Based Project Support

Execution API



    Step 2C: Launching the application in the browser



This is how one can launch the application in the browser


.....

import org.openide.awt.HtmlBrowser.URLDisplayer;

.....



public final class MyAction extends NodeAction {



    .....



    public void launchApp(Project project) {

        Lookup projectLookup = project.getLookup();

        J2eeModuleProvider moduleProvider = (J2eeModuleProvider)projectLookup.lookup(J2eeModuleProvider.class);

        String contextRoot = moduleProvider.getConfigSupport().getWebContextRoot();

        String httpPort = "8080"; // hardcoded. Correct way of getting it is : mbsc.getAttribute(new javax.management.ObjectName("com.sun.appserv:type=http-listener,id=http-listener-1,config=server-config,category=config") ,"port");

        String url = "http://localhost:" + httpPort + "/" + contextRoot;

        try {

            URLDisplayer.getDefault().showURL(new URL(url));

        } catch (MalformedURLException ex) {

            ex.printStackTrace();

        }

    }

    .....

}





Additional libraries needed for this purpose


UI Utilities API



    Step 2D: Invoking a tool by creating a new process



This is pretty simple. We can just use the standard JDK Runtime APIs.


        String commandString = System.getProperty("java.home") + File.separator + "bin" + File.separator + "java" + " -classpath " + "<classpath>" + " avkplugin.Tool";
        Process p = Runtime.getRuntime().exec(commandString);
        p.waitFor();



    Step 2E: Lauching a window in the editor pane



This can be done using the swing UI builder capabilities of the NetBeans IDE.



    Step 3: Source code linking



If className, methodName and argument types are known then we can open the source file and take the cursor to the exact method. This is how we can achieve that:


.....

import org.netbeans.jmi.javamodel.Element;

import org.netbeans.jmi.javamodel.JavaClass;

import org.netbeans.jmi.javamodel.Method;

import org.netbeans.jmi.javamodel.Resource;
import org.netbeans.jmi.javamodel.Type;

import org.netbeans.modules.java.JavaEditor;

import org.netbeans.modules.javacore.api.JavaModel;

import org.netbeans.modules.javacore.internalapi.JavaMetamodel;

import org.openide.loaders.DataObject;

import org.openide.text.PositionBounds;

.....



public final class MyAction extends NodeAction {

    .....

    private List getMethodParams(String[] paramTypes) {
        List methodParams = new ArrayList();
        for(int i=0; i<paramTypes.length; i++) {
            String param = paramTypes[i];
            Type type = JavaModel.getDefaultExtent().getType().resolve(param);
            methodParams.add(type);
        }
        return methodParams;
    }


    public void openSource(String className, String methodName, String[] methodParamTypes) {
        List methodParams = getMethodParams(methodParamTypes);
        JavaClass javaClass = (JavaClass)JavaModel.getDefaultExtent().getType().resolve(className);
        FileObject fo = JavaModel.getFileObject(javaClass.getResource());

        try {

            if(methodName != null) {

                Resource resource = JavaModel.getResource(fo);

                JavaMetamodel javaMetamodel = JavaMetamodel.getManager();

                Method m = javaClass.getMethod(methodName,methodParams,true);

                Element element = resource.getElementByOffset(m.getStartOffset());

                PositionBounds position = javaMetamodel.getElementPosition(element);

                ((JavaEditor) javaMetamodel.getDataObject(element.getResource()).getCookie(JavaEditor.class)).openAt(position.getBegin());

            } else {

                DataObject dobj = DataObject.find(fo);

                OpenCookie oc = (OpenCookie)dobj.getCookie(OpenCookie.class);

                oc.open();

            }

        } catch (Throwable t) {

            StatusDisplayer.getDefault().setStatusText("Unable to open " + className);

            t.printStackTrace();

        }

    }



    .....

}



Additional libraries needed for this purpose


JMI for Java Language Model
Java Language Model Implementation
JMI Reflective API
Text API
Java Source Files
Datasystems API
Window System API
JMI Utilities



Note :  Add <compile-dependency/> for <code-name-base>org.netbeans.modules.jmiutils</code-name-base>


Conclusion :

I hope the NetBeans plug-in developers find some of the tips and the utility methods described above useful. Please provide me your comments/feebback.