OVF API Overview

The following sections provide an overview of the OVF programming interface and related topics:

Runtime Environments

The OVF API is designed to run in either an Eclipse runtime or in a normal Java application runtime environment. The implementation is packaged in one or more jar files that work in both environments, as explained below.

Note that even when developing code for a non-Eclipse application, you can use an Eclipse IDE with the OVF plugins installed into it to provide a convenient user interface for editing OVF descriptors and packages.

Eclipse Runtime Environment

The OVF API support is packaged in one or more Eclipse version 3.4 (Ganymede) plugins that must be installed into the runtime environment and, for development and debugging, into the target platform.

The OVF API support is provided by these plugins:

Other plugins provide support for editing descriptors and packages within an eclipse environment.

These API plugins have dependencies on other platform plugins that must be separately installed.

Eclipse Plugin Dependencies
Plugin Dependencies
com.ibm.adt.ovf.model

org.eclipse.core.runtime

org.eclipse.emf.ecore

org.eclipse.emf.ecore.xmi

org.apache.ant

Java Application Runtime Environment

The OVF API also works in a standard Java application runtime environment. In this environment, the dependent plugins (see above) are configured on the classpath.

The OVF Toolkit includes a new project wizard that creates a Java (not a Plugin) project in your workspace with the necessary dependencies. You can use this sample project to examine how to construct a standard Java runtime environment for the OVF toolkit.

The Tiniest Ever EMF Introduction

The OVF API implementation is based on the Eclipse Modeling Framework (EMF) which may sound scarier than it really is. With only a few exceptions, you will need to know nothing at all about EMF. However, you may well benefit from studying EMF in more detail to learn to take advantage of its many features. This advice applies to both the Eclipse and Java runtime environments. This short section will give you some hints about the role EMF plays in the OVF API implementation.

EMF is all about tooling to support model-driven development. EMF can generate and transform object models based on Java, UML, and XML schema. For OVF, we started with the XML Schema documents included with the DMTF standard. At development time, we used EMF to transform these schema docuements into an internal model (the so-called OVF Metamodel). Also at development time, we used EMF to transform the OVF Metamodel into Java interfaces and classes that comprise the OVF API and so are of prime importance to you. For example there is an XML type named EnvelopeType in the envelope.xsd schema file. EMF generated a corresponding interface, EnvelopeType.java, and an implementation class, EnvelopeTypeImpl.java. EMF generates interface and implementation classes for all other XML types. As an OVF API user, you should only need to refer to the interface classes and be unconcerned with the generated implementation classes.

At runtime, EMF provides the support for parsing and generating OVF files that conform to the OVF XML schema. As an OVF API programmer, you never have to bother with XML; Instead, your code interacts with a runtime model based on the Java classes defined in the API, e.g.EnvelopeType, SectionType, etc. EMF works in the background to create and manage the model.

It is worth noting that EMF metamodels, like the OVF Metamodel, are more expressive than XML Schema 1.0 and that when the OVF Metamodel was generated from the schema files, all the implicit and explicit schema constraints are represented in the OVF Metamodel. Consequently, the XML schema files are not used at runtime for validating XML documents. Instead, the EMF runtime validation function takes care of performing all the same validation checks by using the OVF Metamodel. More than that, we have added more semantic constraints defined by the OVF specification that are not expressed in the schema.

All of the common EMF functions are generic in the sense their implementations are independent of any particular metamodel such as the OVF Metamodel. For example, there are generic EMF functions that allow you to walk the containment structure of an OVF envelope. These functions work because the OVF Metamodel that was used to generate Java code at development time is also available at runtime for EMF to inspect. A good analogy is perhaps the java.lang.reflect package. The classes in this package, e.g. Method, define (part of) the Java runtime metamodel. A Java program can access and manipulate any Java object with the reflective API, e.g. obj.getClass().getDeclaredField("foo").set(obj, "bar"). It is quite possible you will never need to use the analogous EMF reflective interfaces, but they are available when you do. You are likely, however, to use the OVF Metamodel. The generated class, org.dmtf.schemas.ovf.envelope.EnvelopePackage and a few other package classes encapsulate the OVF Metamodel. You will use the OVF Metamodel in various places, for example to create objects.

EMF provides built-in functions to implement the Observer design pattern. If you need to implement the Observer pattern, please checkout the Events and Adapters section.

What is DocumentRoot?

The OVF Metamodel has a class that has no explicit representation in the XML schema: DocumentRoot. As its name suggests, this class represents the root of the XML document. Since the OVF specification states that an Envelope element shall be the root, why should we need another class for this purpose? There are several reasons. First, the OVF specification states the root element is Envelope, but the schema file, envelope.xsd, does not. In fact the envelope.xsd schema file defines many global elements. Any XML file that has one of these global elements as its root is conformant to the schema, even though it is not conformant to the specification. Accordingly, EMF handles parsing and generating any of these alternative nonconformant forms.

Another reason for DocumentRoot is that it provides a container for defining metadata associated with XML substitution groups. See below for more information about OVF substitution groups.

With a reference to a DocumentRoot instance, you can obtain a reference to an EnvelopeType instance with the accessor method: DocumentRoot.getEnvelope().

Loading and Saving Documents

Loading/parsing and saving/generating OVF documents are handled with the EMF persistance framework which defines resources and resource sets. An OVF document instance that is to be parsed or generated is represented by an EMF Resource instance. The aptly named EMF ResourceSet provides the context for managing multiple resources.

To load an OVF document, follow these steps:

  1. Create an instance of ResourceSet
  2. Initialize the resource set to map the file extension .ovf to a factory class for creating OVF envelopes
  3. Initialize the resource set to map the OVF schema nsURI to our OVF Metamodel
  4. Create an instance of Resource by parsing an OVF descriptor.
  5. Access the parsed OVF Envelope from the Resource. Note that root element of an OVF descriptor in our metamodel is an instance of type DocumentRoot. From the DocumentRoot, you can access the envelope object via DocumentRoot.getEnvelope().

The following example shows each of these steps:

		
		// 1. Create a resource set to hold the resources.
		// A ResourceSet is where EMF holds persisted objects. As the name suggests,
		// it is a set of EMF Resources. When you load an OVF file, it will represented
		// as a Resource in a ResourceSet.
		ResourceSet resourceSet = new ResourceSetImpl();
		
		// 2. Register the appropriate resource factory to handle all file extensions.
		// Since we are running outside of eclipse, we have to tell EMF how to 
		// associate files with models (like OVF). The DEFAULT_EXTENSION is "*" so
		// EMF will associate our Envelope stuff with any file.
		resourceSet.getResourceFactoryRegistry().getExtensionToFactoryMap().put
			(Resource.Factory.Registry.DEFAULT_EXTENSION, 
			 new EnvelopeResourceFactoryImpl());

		// 3. Register the package to ensure it is available during loading.
		// Since we are running outside of eclipse, we need to register our OVF metamodel
		// with the nsURI that identifies it. For OVF, this is "http://schemas.dmtf.org/ovf/envelope/1".
		resourceSet.getPackageRegistry().put
			(EnvelopePackage.eNS_URI, 
			 EnvelopePackage.eINSTANCE);

        File file = new File("foobar.ovf");
        
        // Get an EMF URI (which is a different class from the java.net.URI) from the arguments.
        URI uri = URI.createFileURI(file.getAbsolutePath());

        try {
            // 4. Demand load a resource for this file. ResourceSet.getResource() will locate the resource
            // if it has already been loaded, or it will load it from the source specified by the URI.
            //
            Resource resource = resourceSet.getResource(uri, true);
            
            // 5. If we assume the resource corresponds to an OVF descriptor, we can get 
            // a reference to the DocumentRoot
            DocumentRoot documentRoot = (DocumentRoot) resource.getContents().get(0);
            
            // Get the envelope
            EnvelopeType envelope = documentRoot.getEnvelope();

        catch (RuntimeException exception) {
            System.out.println("Problem loading " + uri);
            exception.printStackTrace();
        }
		

The above example fails to illustrate all the options available to you for controlling the loading process. You may, for example, provide your own java.io.InputStream as the source for the model. You may also customize error handling for unknown elements.

Validation

Validation occurs at two points during a model's lifecycle: on model loading and on demand.

The first validation check occurs automatically when you load a model from it's XML source. This validation amounts to only syntactically checking that a model can be created from the source. There are no semantic checks made during loading.

Once you have a model in the runtime environment, you can validate all or part of it on demand. This kind of validation ensures that the model satisfies the many semantic constraints defined by the OVF standard. These constraints include those that are explicitly represented by the OVF XML schema, e.g. that an Envelope element contains exactly one References element. It also verifies other semantic constraints that are documented only by text in the specification, e.g. that an Envelope element may contain a Disk Section, but a Virtual System Collection must not.

The EMF Diagnostician class validates any given object in the model and all its contained instances. You may validate an entire descriptor by passing a reference to the EnvelopeType to the Diagnostician, as the following example shows.

import org.dmtf.schemas.ovf.envelope.EnvelopeType;
import org.eclipse.emf.common.util.Diagnostic;
import org.eclipse.emf.ecore.util.Diagnostician;
...
    // You can validate on demand using the Diagnostician 
    public void validate(EnvelopeType envelope) {
        Diagnostic diagnostic = Diagnostician.INSTANCE.validate(envelope);
        if (diagnostic.getSeverity() != Diagnostic.OK) {
            printDiagnostic(diagnostic, "");
        }
    }
...

The Diagnostic class contains summary information, e.g. the overall severity, and more detailed nested diagnotics.

This example shows how to recursively print out some details in a Diagnostic.

	public static void printDiagnostic(Diagnostic diagnostic, String indent) {
		System.out.print(indent);
		System.out.println(diagnostic.getMessage());
		for (Diagnostic child : diagnostic.getChildren()) {
			printDiagnostic(child, indent + "  ");
		}
    }

Creating Objects

Creating OVF objects is easy, but you have to remember not to use the implemenation classes and their Java constructors.

There are factory classes for the OVF packages that provide the necessary creation methods. By using the factory classes, you ensure that EMF completely initializes the new objects.

The pattern to follow is this:

EnvelopePackage.eINSTANCE.createFoo()

to create an instance of Foo.

Navigating a Model

You access and modify the OVF model with the methods defined on the generated interfaces. You can certainly get by with only studying the Java interfaces in the OVF API, but to better appreciate what is going on you should know a little more about how they relate to the OVF Metamodel. The next few paragraphs provide a little more background information.

The OVF Metamodel consists mostly of EClasses, EAttributes, EOperations, and EReferences (the "E" prefix is an EMF naming convention to minimize confusion with much-overloaded terminology).

An EClass is an EMF Class and is much like a Java class. Example EClasses in the OVF Metamodel include: EnvelopeType, DiskSectionType, FileType. An EClass is represented in Java by an interface definition with the same name. The implementations behind EClasses are generated and managed by EMF and are named after their corresponding interfaces, e.g. EnvelopeTypeImpl. Although you may access the implementation classes directly, you should avoid doing so. Instead, try to use only the Java intefaces.

An EClass contains EAttributes and EOperations much like a Java class contains fields and methods.

An EAttribute is a property of an EClass that is a simple data type, like Integer or String. It is roughly similar to the Java field. The generated Java interfaces contain getter and setter methods for the EAttributes. For example, the FileType EClass contains an EAttribute named id of type String. The FileType interface includes the methods, String getId() and void setId(String value).

An EOperation is some behavior associated with an EClass, much like a Java method. In fact, it maps very directly to Java methods. For example, the EnvelopeType EClass contains an EOperation named getAllSections().

An EReference is a relationship between one or two EClasses (in the case of a reflexive relationship, both ends of the relationship are the same EClass). An EReference may be a containment or non-containment. Containment connotes a parent/child relationship where the child exists in the model only so long as the parent exists in the model, and any object in the model has exactly one parent (except for the root object). Nested XML elements in the OVF schema map to containment relationships, e.g. the Envelope element contains one instance of the References element. Accordingly, there is a containment relationship between the EnvelopeType and ReferencesType EClasses in the OVF Metamodel. When you save an OVF model to an XML file, the containment structure determines what gets written.

As with EAttributes, the generated Java interfaces provide getter and setter methods to establish and navigate EReferences. For example, to navigate from an EnvelopeType instance to its ReferencesType the generated Java method is EnvelopeType.getReferences().

An EReference has a multiplicity property that defines the minimum and maximum number of instances for the participant. For relationships that can have more than one participant, the getter method will return a parameterized EList. The EList class is an EMF subclass of the java List interface. From an API perspective, you add and remove participants with theEList.add() and Elist.remove() methods. For relationships that have zero or one participant, the getter and setter methods use only a single instance of the participant's type. This example shows adding and removing instances in a multiplicity many relationship.

  ReferencesType refs = EnvelopeFactory.eINSTANCE.createReferencesType();
  FileType file1 = EnvelopeFactory.eINSTANCE.createFileType();
  FileType file2 = EnvelopeFactory.eINSTANCE.createFileType();
  
  // Initially there are no files in the references section
  assertTrue(refs.getFile().size() == 0);
  
  // Add the new file 
  refs.getFile().add(file1);
  
  // Prove it was added
  assertTrue(refs.getFile().size() == 1 && refs.getFile().get(0) == file1);
  
  // Add another one
  refs.getFile().add(file2);

  assertTrue(refs.getFile().size() == 2 && refs.getFile().get(1) == file2);
  
  // Delete all references
  refs.getFile().clear();

  assertTrue(refs.getFile().size() == 0);

 

An EReference may be defined so it is navigable from both ends. For example, the EReference between EnvelopeType and ReferencesType can be navigated both ways: EnvelopeType.getReferences() and ReferencesType.getEnvelope(). EMF handles all of the bookeeping for you when you modify one end of a relationship that requires updating the other end. This example illustrates how to use this feature.

  EnvelopeType env = EnvelopeFactory.eINSTANCE.createEnvelopeType(); // create an envelope
  ReferencesType refs = EnvelopeFactory.eINSTANCE.createReferencesType(); // create references
  
  assertNull(env.getReferences()); // no references initially
  assertNull(refs.getEnvelope()); // no envelopee initially
  
  // One way to create the reference is to set the EnvelopeType end
  // Equivalently, we could create the reference by setting the 
  // ReferencesType end with refs.setEnvelope(env)
  env.setReferences(refs); // create the reference
  
  // Prove that both ends are set by navigating from
  // the ReferencesType to the EnvelopeType
  assertTrue(env == refs.getEnvelope()); // navigate from refs to envelope
  
  // Delete the reference by setting one of the ends to null
  refs.setEnvelope(null); // remove the reference
  
  // Prove that both ends are deleted 
  assertNull(env.getReferences()); // EMF handles deleting both ends for you
  

Substitution Groups

Substitution groups are XML Schema feature employed by the OVF specification for Section and Content elements. Each of the Section types has two forms for its XML encoding. For example, a Disk section may be encoded without a substitution group as:

<ovf:Section xsi:type="DiskSection_Type">

Equivalently, a Disk section may be encoded using a substitution group as:

<ovf:DiskSection>

The OVF Metamodel and API support both encoding forms. To correctly use the API, you must understand how substitution groups are represented.

The EnvelopeType.getSection() method returns all sections whether or not they are defined with substitution groups. The return type of this method is EList<SectionType>.

The EnvelopeType.getSectionGroup() method also returns all sections. The return type of this method is a special kind of list called a FeatureMap, which is explained below.

First, the same OVF EClass and corresponding Java interface is used to represent both forms. This means that both of the Disk section examples above would be represented in the API as instances of DiskSectionType. And you create a Disk section the same way, no matter how it will be represented in the XML file (that is, using the EnvelopeFactory.eINSTANCE.createDiskSectionType()).

The difference of whether a element will be encoded as a substitution group or not depends on which EReference refers to the element. Every substitution group has an EReference defined on the DocumentRoot EClass. For example, there is a DocumentRoot.diskSection EReference defined for Disk sections that are represented with the <ovf:DiskSection>substitution groups.

Each entry in this list is of type FeatureMap.Entry. A FeatureMap.Entry is a simple object that records an EReference and a value. The EReference indicates whether a substitution group was used or not. For example, as mentioned, the EnvelopeType.getSectionGroup() returns a FeatureMap. Each entry in the FeatureMap list is of type FeatureMap.Entry. For a Disk section there is a corresponding FeatureMap.Entry whose value is of type DiskSectionType. If the Disk section was encoded as a substitution group, i.e. as <ovf:DiskSection>, that entry will have an EReference equal to EnvelopePackage.Literals.DOCUMENT_ROOT__DISK_SECTION. If the same Disk section was encoded as a <ovf:Section> element, the EReference would be equal to EnvelopePackage.Literals.ENVELOPE_TYPE__SECTION.

The ContentType EClass also includes sections that may be substitution groups, so the above discussion applies equally to it and its subclasses, VirtualSystemCollectionType and VirtualSystemType.

The following example shows how substitution groups work for sections in an envelope.


...
import org.eclipse.emf.ecore.util.FeatureMap;
...

	DiskSectionType diskSection = EnvelopeFactory.eINSTANCE
			.createDiskSectionType();

	NetworkSectionType networkSection = EnvelopeFactory.eINSTANCE.
			createNetworkSectionType();
	
	DeploymentOptionSectionType deploymentOptions = 
		EnvelopeFactory.eINSTANCE.createDeploymentOptionSectionType();
	
	// The EnvelopeType.sectionGroup is a list where each element
	// identifies a structural feature with the value.
	// The OVF schema defines Sections as substitution groups and as
	// generic Section types. 
	// a DocumentRoot reference.
	// 
	FeatureMap sectionMap = envelope.getSectionGroup();
	
	// Each substitution group is defined by a documentRoot reference.
	// Add a disk section as a "strongly typed" substitution group
	sectionMap.add(EnvelopePackage.Literals.DOCUMENT_ROOT__DISK_SECTION,
			diskSection);
	
	// Add a network section as a loosely typed element
	sectionMap.add(EnvelopePackage.Literals.ENVELOPE_TYPE__SECTION,
			networkSection);
	
	// There are two sections defined: one generic, one substitution group
	assert(envelope.getSectionGroup().size() == 2);
	
	assert(envelope.getSection().size() == 2);
	
	// You can also use the derived feature to add new elements
	envelope.getSection().add(deploymentOptions);
	
	// Verify the deployment options were added as a generic section type
	FeatureMap.Entry entry =envelope.getSectionGroup().get(2);
	assert(entry.getEStructuralFeature() == 
		EnvelopePackage.Literals.ENVELOPE_TYPE__SECTION);
	assert(entry.getValue() == deploymentOptions);

The above example creates three sections in an envelope, two of which are loosely typed, and one that is strongly typed. It produces the following OVF descriptor.

<?xml version="1.0" encoding="ASCII"?>
<ovf:Envelope xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:ovf="http://schemas.dmtf.org/ovf/envelope/1">
<ovf:DiskSection/>
<ovf:Section xsi:type="ovf:NetworkSection_Type"/>
<ovf:Section xsi:type="ovf:DeploymentOptionSection_Type"/>
</ovf:Envelope>

Events and Adapters

EMF provides the infrastructure to easily build event-driven code (the so-called Observer design pattern). In addition, the same infrastructure provides a way to add behavior to the OVF Metamodel without subclassing its implementation.

An operation that modifies an OVF object will generate an event if there are any listeners registered for the object. In EMF, these listeners are called adapters and implement the org.eclipse.emf.common.notify.Adapter interface.Usually, it is most convenient to create an adapter by subclassing the org.eclipse.emf.common.notify.AdapterImpl. class. To employ the adapter, you attach an adapter instance to an OVF object instance by adding it to the EObject.eAdapters() list.

The following example adapter implementation overrides the notifyChanged method and trivially prints out the event type.

	class MyAdapter extends AdapterImpl {

		String name;

		public MyAdapter(String name) {
			this.name = name;
		}

		/*
		 * (non-Javadoc)
		 * 
		 * @see
		 * org.eclipse.emf.common.notify.impl.AdapterImpl#notifyChanged(org.
		 * eclipse.emf.common.notify.Notification)
		 */
		@Override
		public void notifyChanged(Notification msg) {
			super.notifyChanged(msg);
			System.out.println(name + ": " + getEventTypeName(msg));
		}

		private String getEventTypeName(Notification msg) {
			String eventType;
			switch (msg.getEventType()) {
			case Notification.SET: {
				eventType = "SET";
				break;
			}
			case Notification.UNSET: {
				eventType = "UNSET";
				break;
			}
			case Notification.ADD: {
				eventType = "ADD";
				break;
			}
			case Notification.ADD_MANY: {
				eventType = "ADD_MANY";
				break;
			}
			case Notification.REMOVE: {
				eventType = "REMOVE";
				break;
			}
			case Notification.REMOVE_MANY: {
				eventType = "REMOVE_MANY";
				break;
			}
			case Notification.MOVE: {
				eventType = "MOVE";
				break;
			}
			case Notification.REMOVING_ADAPTER: {
				eventType = "REMOVING_ADAPTER";
				break;
			}
			case Notification.RESOLVE: {
				eventType = "RESOLVE";
				break;
			}
			default: {
				eventType = "Unknown";
				break;
			}
			}
			return eventType;
		}
	}

The following example shows how to use the example adapter with a few OVF objects.

		ReferencesType refs = EnvelopeFactory.eINSTANCE.createReferencesType();
		MyAdapter refsAdapter = new MyAdapter("refs adapter");
		refs.eAdapters().add(refsAdapter);
		System.out.println("Added refs adapter");

		FileType file1 = EnvelopeFactory.eINSTANCE.createFileType();
		MyAdapter file1Adapter = new MyAdapter("file1 adapter");
		file1.eAdapters().add(file1Adapter);
		System.out.println("Added file1 adapter");

		refs.getFile().add(file1); // fires ADD event

		file1.setId("file1"); // fires SET event
		file1.getId(); // no event fired

		refs.getFile().clear(); // fires REMOVE event for every FileType removed

		// Clean up by removing adapters
		refs.eAdapters().remove(refsAdapter); // fires REMOVING_ADAPTER
		file1.eAdapters().remove(file1Adapter); // fires REMOVING_ADAPTER

The above code creates the following console output.

Added refs adapter
Added file1 adapter
file1 adapter: SET
refs adapter: ADD
file1 adapter: SET
file1 adapter: SET
refs adapter: REMOVE
refs adapter: REMOVING_ADAPTER
file1 adapter: REMOVING_ADAPTER

If you need to observe structural changes to a whole subtree of OVF objects, EMF provides the handy utility class, org.eclipse.emf.ecore.util.EContentAdapter. This adapter handles additions and removals within a tree of objects. The following example adapter is a simple subclass of EContentAdapter.

	class MyContentAdapter extends EContentAdapter {

		String name;

		public MyContentAdapter(String name) {
			this.name = name;
		}

		/*
		 * (non-Javadoc)
		 * 
		 * @see
		 * org.eclipse.emf.common.notify.impl.AdapterImpl#notifyChanged(org.
		 * eclipse.emf.common.notify.Notification)
		 */
		@Override
		public void notifyChanged(Notification msg) {
			super.notifyChanged(msg);
			System.out.println(name + ": " + getEventTypeName(msg) + " for "+msg.getNotifier());
		}
		...
	}

The following example shows attaching the content adapter.

		ReferencesType refs = EnvelopeFactory.eINSTANCE.createReferencesType();
		MyContentAdapter refsAdapter = new MyContentAdapter("refs content adapter");
		refs.eAdapters().add(refsAdapter);
		System.out.println("Added refs content adapter");

		FileType file1 = EnvelopeFactory.eINSTANCE.createFileType();

		refs.getFile().add(file1); // fires ADD event

		file1.setId("file1"); // fires SET event

		refs.getFile().clear(); // fires SET event for setting list to null

		// Clean up by removing adapters
		refs.eAdapters().remove(refsAdapter); // fires REMOVING_ADAPTER

The following console output shows the results of executing the above code.

Added refs content adapter
refs content adapter: ADD for org.dmtf.schemas.ovf.envelope.impl.ReferencesTypeImpl...
refs content adapter: SET for org.dmtf.schemas.ovf.envelope.impl.FileTypeImpl...
refs content adapter: SET for org.dmtf.schemas.ovf.envelope.impl.FileTypeImpl...
refs content adapter: REMOVING_ADAPTER for org.dmtf.schemas.ovf.envelope.impl.FileTypeImpl...
refs content adapter: REMOVE for org.dmtf.schemas.ovf.envelope.impl.ReferencesTypeImpl...
refs content adapter: REMOVING_ADAPTER for org.dmtf.schemas.ovf.envelope.impl.ReferencesTypeImpl...

Switch Classes

The OVF API includes switch classes generated by EMF which at first may seem little more than curiosities, but with a little explanation you should be convinced of their utility.

Each EMF package in the OVF Metamodel includes a utility switch class:

As each of the above classes has a similar purpose and structure, we will focus on the EnvelopeSwitch class in the following discussion.

Though the EnvelopeSwitch class is not abstract, its usefulness arises only when it is subclassed. For subclasses, it provides a function similar to the standard Java switch statement. As such, you can execute code specific to the object type for an instance passed to the switch class.

The first thing to note about the switch class is the doSwitch(EObject) method. In the Java analog, this corresponds to the switch(int){...} part of the switch statement. Second, notice that the argument is an EObject which allows us to perform a switch on any EObject, not just those in the envelope package.

The EnvelopeSwitch class includes a case method for each of the EClasses in the envelope package of the OVF Metamodel, for example:

This hints at how to employ the switch. To provide code specific to DiskSectionType, for example, your subclass overrides the caseDiskSectionType(DiskSectionType) method. Unlike Java, the switch and the case methods return an object of type java.lang.Object. Besides being a nice feature, this is important for how the switch behaves, as described below.

In Java, the switch statement expression must be an integral type, e.g. int or char. And the case expressions are distinct constants. In our EMF switch, the cases are EMF object types which are also distinct, but because the are object types, they may also have interitance relationships. For example, DiskSectionType is a subclass of SectionType. How should the switch class behave when passed an instance of DiskSectionType? Certainly you would expect it to invoke the caseDiskSectionType() method. But should it also invoke the caseSectionType() method?

The EMF switch statement implementation of the doSwitch() method will invoke the case methods that can possible apply in order of the most specific subclass to the most generic superclass, but it stops whenever a case method returns a non-null value. A non-null case method result means "case handled" and the value is returned as the result of the doSwitch() method. The default implementation for each of the case methods returns null.

Here is a simple example of a switch class that overrides three case methods and the default method. It uses java.lang.String as its type parameter, though another switch subclass could use any other type.

	EnvelopeSwitch<String> sw1 = new EnvelopeSwitch<String>() {
	
		@Override
		public String caseAnnotationSectionType(AnnotationSectionType object) {
			System.out.println("Annotation section found, not handled");
			return null;
		}
	
		@Override
		public String caseDiskSectionType(DiskSectionType object) {
			System.out.println("Disk section found, handled");
			return "disk";
		}
	
		@Override
		public String caseSectionType(SectionType object) {
			System.out.println("Generic section, handled");
			return "section";
		}
		
		@Override
		public String defaultCase(EObject object) {
			System.out.println("default case, not handled");
			return null;
		}
	};

The following example shows uses of the above switch implementation.

    AnnotationSectionType annoSection = EnvelopeFactory.eINSTANCE.createAnnotationSectionType();
    DiskSectionType diskSection = EnvelopeFactory.eINSTANCE.createDiskSectionType();
    NetworkSectionType networkSection = EnvelopeFactory.eINSTANCE.createNetworkSectionType();
    ReferencesType refs = EnvelopeFactory.eINSTANCE.createReferencesType();		
    String result;
    
    System.out.println("-- Annotation Section switch");
    result = sw1.doSwitch(annoSection);
    System.out.println("-- Annotation Section result: "+result);
    assert("section".equals(result));
    System.out.println("\n-- Disk Section switch");
    result = sw1.doSwitch(diskSection);
    System.out.println("-- Disk Section result: "+result);
    assert("disk".equals(result));
    System.out.println("\n-- Network Section switch");
    result = sw1.doSwitch(networkSection);
    System.out.println("-- Network Section result: "+result);
    assert("section".equals(result));
    System.out.println("\n-- References switch");
    result = sw1.doSwitch(refs);
    System.out.println("-- References result: "+result);
    assert(result == null);

The above sample code produces the following output

-- Annotation Section switch
Annotation section found, not handled
Generic section, handled
-- Annotation Section result: section

-- Disk Section switch Disk section found, handled -- Disk Section result: disk
-- Network Section switch Generic section, handled -- Network Section result: section
-- References switch default case, not handled -- References result: null

This exemplifies several points. For the annotation section instance, the switch class first invokes the caseAnnotationSectionType() method. Since that returns null, the switch class then invokes the caseSectionType() method because SectionType is a superclass of AnnotationSectionType. The caseSectionType() method returns a non-null value which then becomes the result of the doSwitch().

The disk section example is slightly different in that the caseDiskSectionType() method returns a non-null value, thus circumventing a call to caseSectionType().

The network section example illustrates that the default implementation of caseNetworkSectionType() returns null, causing the switch statement to then invoke the caseSectionType() method (SectionType is a superclass of NetworkSectionType).

The references example shows how the defaultCase() works. The caseReferencesType() is not overridden, so the default implementation returns null. The ReferencesType class has no superclass, so there are no other case methods to try. As a last resort, the defaultCase method is invoked and its result then becomes the result returned from the doSwitch() method.

As a regular Java class, a switch subclass can have state information and other methods, making it a natural starting point to implement the visitor design pattern, for example.

Creating an OVF metadata file from a VMware VMX configuration file

It is recognized that many x86 based virtual machines already exist and are hosted by one of the VMware hypervisor products.  In many cases, it will be desirable to create the OVF envelope metadata from these previously built virtual machines, in contrast to creating the metadata from the ground up.  To facilitate this metadata generation, we have implemented a utility class called "ImportVMwareAction" that can do this generation for you.

Using this utility class is relatively easy and straight forward.  The primary steps would involve:

The following sample illustrates the use of this utility

   import com.ibm.adt.ovf.model.utility.ImportVMwareAction;
   
   ImportVMwareAction importVMwareAction = new ImportVMwareAction("c:/vm/myvm.vmx"); 
   
   DocumentRoot documentRoot = null;
   
   try {
     documentRoot = importVMwareAction.parseVmxFile();
   }
   catch (IOException e) {
     System.out.println("System error encountered : " + e.getMessage());
   }
   catch (RuntimeException e) {
     System.out.println("System error encountered : " + e.getMessage());
   }
   catch (URISyntaxException e) {
     System.out.println("System error encountered : " + e.getMessage());
   }

Now, what you choose to do with the created DocumentRoot (or more specifically the EnvelopeType within the DocumentRoot) will vary based on your requirements.  The utility class makes no assumptions as to what to do with the created metadata, and leaves that choice to the implemetation.  A typical scenario could be the following:

Exporting an OVF Package

The OVF API includes functions for exporting an OVF descriptor and supporting artifacts to a compliant package format. Features include:

These are the steps necessary to export an OVF package with the OVF API:

  1. Create or load an instance of an OVF Envelope model
  2. Create an instance of the utility class com.ibm.adt.ovf.model.utility.PackageExporter
  3. Invoke the PackageExporter.export() method.

Importing an OVF Package

The OVF API includes functions for importing an archived OVF package, including these features:

These are the steps necessary to import an OVF package with the OVF API:

  1. Create an instance of the utility class com.ibm.adt.ovf.model.utility.PackageImporter
  2. Invoke the the PackageImporter.import() method.