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:
- Design a file format of the project file (typically one would
choose to store the project metadata in form of XML - in that case
this step includes creation of XML schema)
- Create a project file reader (e.g. built on top of an XML
parser). In case the project file can be too big, one
needs to create a sophisticated loading mechanism that loads parts
of the file as needed to avoid running out of memory.
- Design the API that other modules will use to access the project
metadata.
- Implement the API - provide an in-memory representation of
project metadata.
- Implement a serialization mechanism to be able to save the
metadata upon shutdown of the application.
This is quite a lot of coding. Now here is how one can implement the
module using MDR:
- Create a MOF model of the project metadata (in a UML tool).
- Convert the UML XMI produced by the UML tool into a MOF XMI
using UML2MOF transformation tool (see
http://mdr.netbeans.org/uml2mof).
- Load the model into MDR and generate JMI interfaces from the
model. This will be the API that will be used by this and other modules
to access the project metadata.
- XMI reader and writer provided by MDR can be then used by module
to load/save the project metadata from/into an XML file. The format
is automatically derived from the model of the project metadata
according to OMG XMI specification.
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:
- Create a new instance of "Project" package in "Project model" extent and name it "Projects metadata" (so that repository.getExtent("Projects metadata") call in the
example above does not return null).
- Set the class to be run using internal execution (in case you are running it from NetBeans) - you can do this by selecting
the file in NB explorer where the class is defined and setting Executor property in the Execution tab of property sheet for this file to Internal Execution.
- Add the generated JMI API for projects metamodel to the NetBeans internal classpath. This can be done in two ways:
- Add the API to a jar file, shutdown NetBeans, copy the jar into netbeans/lib/ext directory, start NetBeans again, or
- Package the API as a NetBeans module by adding it to a jar with the manifest that can be downloaded here and install this jar file into
NetBeans as a module (go to main menu->Tools->Options, right-click on IDE Configuration->System->Modules, choose Add->Module..., browse to the jar file).
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);