Example of Using MDR

Let's say one would like to create a NetBeans module to manage information about projects open in the IDE. Such module should be able to open a project from a project file, read the project classpath and all the other settings from the project file and expose those settings to other modules via a standard API.
Let's look at what would be needed to write such module manually: This is quite a lot of coding. Now here is how one can implement the module using MDR: As you can see, the main task you need to do is to create a model of the project metadata. Then the API, file format and in-memory representation are all derived from the model according to JMI, XMI and MOF standards and all the lifecycle management of objects and load/save functionality is automatically provided by MDR.

Here is an example of a very simple model of the project metadata (modeled in UML):

It contains a class named Project, representing a project. The class may or may not be a singleton depending on whether the IDE allows more projects being open at the same time. A project is identified by a name represented by name attribute inherited from Element abstract class. URL attributes store the physical URL of the project file. A project contains one to many classpaths (which is expressed using the association line with a black diamond at the Project class denoting a composite end). A classpath consists of zero to many classpath elements.

After we saved the model to the UML XMI (Poseidon zargo file cantaining this model can be downloaded here) and converted it to the MOF XMI (the converted XMI can be downloaded here), we can load it into the MDR and generate the JMI interfaces from it. To do that, make sure that you have MDR modules installed. If so, you should see "MDR Browser" item under "View" menu:

Invoke the MDR Browser. Under the repository node, you should see MOF node, which represents a single instance of a MOF model (containing metadata conforming to MOF itself). It contains the definition of MOF itself. We can now "instantiate" MOF model contained in it to create an empty instance of MOF into which we will load our model of projects. To instantiate MOF, we need to right-click on the Model package of MOF and choose "Instantiate" action from the pop-up menu:

A dialog will appear asking for a name for the new instance - let's call it "Projects model". Now you can right-click on the newly created instance of MOF and import the projects model into it:

To generate interfaces from the projects model we can right-click on the newly imported Projects package under the "Projects model" node and choose "Generate JMI interfaces" from the pop-up menu.

Interfaces for managing and accessing the project metadata by other modules will be generated. For every class in the model two interfaces are generated - one representing a factory and the second representing individual instances. The factory interfaces contain methods for creating new instances of a given class and retrieving list of all existing instances. The instance interface then contains getter and setter methods for its attributes and references (i.e. association ends). Here are the interfaces generated from the Project class of the model:

Factory interface:

/**
 * Project class proxy interface.
 */
public interface ProjectClass extends javax.jmi.reflect.RefClass {
    /**
     * The default factory operation used to create an instance object.
     * @return The created instance object.
     */
    public Project createProject();
    
    /**
     * Creates an instance object having attributes initialized by the passed 
     * values.
     * @param name 
     * @param url 
     * @return The created instance object.
     */
    public Project createProject(java.lang.String name, java.lang.String url);
}

Instance interface:

/**
 * Project object instance interface.
 */
public interface Project extends org.netbeans.jmi.projects.Element {
    /**
     * Returns the value of attribute url.
     * @return Value of attribute url.
     */
    public java.lang.String getUrl();
    
    /**
     * Sets the value of url attribute. See {@link #getUrl} for description on 
     * the attribute.
     * @param newValue New value to be set.
     */
    public void setUrl(java.lang.String newValue);
    
    /**
     * Returns the value of reference classPaths.
     * @return Value of reference classPaths.
     */
    public java.util.Collection getClassPaths();
}

In addition one interface is generated for each association and package in the model.
Implementation of all of these interfaces is provided by MDR during the runtime automatically. All the metadata are stored and managed by MDR in a pluggable storage (a b-tree database by default).

OK, so we have the interfaces now to access the metadata. Here is an example source code that creates a new project using the API:

package org.netbeans.mdrdemo;

import org.netbeans.api.mdr.MDRManager;
import org.netbeans.api.mdr.MDRepository;
import org.netbeans.jmi.projects.*;

public class ProjectCreationDemo {
    
    public static void main(String[] args) {
        // connect to the repository
        MDRepository repository = MDRManager.getDefault().getDefaultRepository();
        
        // get the extent containing projects metadata (instance of
        // "Project" package in "Project model")
        ProjectsPackage extent = (ProjectsPackage) repository.getExtent("Projects metadata");
        
        // get the factory for projects
        ProjectClass projectFactory = extent.getProject();
        // create new project
        Project prj = projectFactory.createProject("MyProject", "");
        
        // get the factory for class path
        ClassPathClass classPathFactory = extent.getClassPath();
        // create new class path
        ClassPath cp = classPathFactory.createClassPath("Default");
        // add the class path to the project
        cp.setProject(prj);
        
        // get the factory for class path element
        ClassPathElementClass cpElementFactory = extent.getClassPathElement();
        // create new class path element
        ClassPathElement cpe1 = cpElementFactory.createClassPathElement("lib1.jar", "");
        // add the element to the class path
        cpe1.setClassPath(cp);
        // create another class path element
        ClassPathElement cpe2 = cpElementFactory.createClassPathElement("lib2.jar", "");
        // add the element to the class path
        cpe2.setClassPath(cp);
    }
}

If you want to run the example above (and also the code below), you first need to do three things:

Here is a code that traverses the project metadata using the JMI API and writes it to the standard output:

package org.netbeans.mdrdemo;

import java.util.Iterator;
import org.netbeans.api.mdr.MDRManager;
import org.netbeans.api.mdr.MDRepository;
import org.netbeans.jmi.projects.*;

public class ProjectRetrievalDemo {
    
    public static void main(String[] args) {
        // connect to the repository
        MDRepository repository = MDRManager.getDefault().getDefaultRepository();
        
        // get the extent containing projects metadata
        ProjectsPackage extent = (ProjectsPackage) repository.getExtent("Projects metadata");
        
        // get the factory for projects
        ProjectClass projectFactory = extent.getProject();
        // get list of all projects
        for (Iterator it = projectFactory.refAllOfClass().iterator(); it.hasNext();) {
            Project prj = (Project) it.next();
            // print the project name
            System.out.println("Found project: " + prj.getName());
            // print out the classpaths in the project
            System.out.println("It contains the following class paths:");
            for (Iterator it2 = prj.getClassPaths().iterator(); it2.hasNext();) {
                ClassPath cp = (ClassPath) it2.next();
                // print the class path name
                System.out.print("    " + cp.getName() + " = ");
                // print out the class path elements
                for (Iterator it3 = cp.getElements().iterator(); it3.hasNext();) {
                    ClassPathElement cpe = (ClassPathElement) it3.next();
                    // print the class path element name
                    System.out.print(cpe.getName() + "; ");
                }
                System.out.println();
            }
        }
    }
}

When a user wants to open a different set of projects, the current projects metadata can be saved to an XML file and replaced by the metadata for the new set of projects in the repository. Saving the metadata is very simple and can be done by a single call to XMIWriter class:

XMIWriterFactory.getDefault().createXMIWriter().write(outputstream, extent, xmiVersion);

The produced XML file will look like the following:

<?xml version = '1.0' encoding = 'ISO-8859-1' ?>
<XMI xmi.version = '1.2' timestamp = 'Wed Sep 17 21:20:34 CEST 2003'>
  <XMI.header>
    <XMI.documentation>
      <XMI.exporter>Netbeans XMI Writer</XMI.exporter>
      <XMI.exporterVersion>1.0</XMI.exporterVersion>
    </XMI.documentation>
  </XMI.header>
  <XMI.content>
    <Projects.Project xmi.id = 'a1' name = 'MyProject' url = ''>
      <Projects.Project.classPaths>
        <Projects.ClassPath xmi.id = 'a2' name = 'Default'>
          <Projects.ClassPath.elements>
            <Projects.ClassPathElement xmi.id = 'a3' name = 'lib1.jar' url = ''/>
            <Projects.ClassPathElement xmi.id = 'a4' name = 'lib2.jar' url = ''/>
          </Projects.ClassPath.elements>
        </Projects.ClassPath>
      </Projects.Project.classPaths>
    </Projects.Project>
  </XMI.content>
</XMI>

To load the project metadata back into the MDR from the XMI file, one just needs to make a call to XMI reader:

XMIReaderFactory.getDefault().createXMIReader().read(inputstream, null, extent);