Introduction

This page presents a simple example as a quick start, an overview of the OMaP architecture, the simple example revisited, the unit tests of the project and the mapping language definition.

Quick Start

Say our application uses objects of the Hello class and needs to export Hello instances to XML files.

The class definition


public class Hello {

  public String getMessage() {
    return message;
  }
  public void setMessage(String message) {
    this.message = message;
  }
  
  public String getName() {
    return name;
  }
  public void setName(String name) {
    this.name = name;
  }

  private String name;
  private String message;

}
  

Mapping the class

Now, let's write the code that writes objects mapped to XML format to the console.

  public static void main(String[] argsthrows IOException {
    
    Hello world = new Hello();
    
    world.setName("MyName");
    world.setMessage("Hello, world!");
    
    XMLMapper mapper = new XMLMapper("/net/sf/omap/example/hello.mds");
    
    mapper.map(world, System.out);
    
  }

The main method first creates a Hello object, next creates a XMLMapper passing the mapping definition as a Mapping Definition Language script.
The last line calls the map method of the mapper object passing the object to map (world) and the output stream where the XML must be written (System.out).

Here is the output we get:

<?xml version="1.0" encoding="Cp1252"?>
<hello name="MyName">
    <message><![CDATA[Hello, world!]]></message>
</hello>

What does the hello.mds file look like?

alias net.sf.omap.example.Hello Hello

Hello -> hello
	name @->
	message

What does the definition mean?

  1. First declares an alias to use as a shortcut, here we say we will use Hello instead of net.sf.omap.example.Hello.
  2. Map the Hello class to a hello XML element.
  3. Map the name property as an attribute with the same name as the property.
  4. Map the message property as child element with the same name as the property.

OMAP Overview

The previous example shows an XML facade (net.sf.omap.xml.XMLMapper) that makes OMaP usage simpler.

Behind that facade, everything is based on the net.sf.omap.MappingExpert interface. An expert object registers all the mappers associated with all types.

A mapper can be registered as a default mapper or can be set in a named category.

A mapper must implement the net.sf.omap.ObjectMapper interface.

Mapper resolution

As we said that a mapper expert contains all the registered mappers that can either be registered in the default mapper category or in a specific one, let's see what mapper resolution is applied when the expert is performing some mapping.

Default mapper

Next example shows a code snippet where the default mapping is used:

    expert.map(anObject);

If the given object (anObject) is null the mapped value (the value returned by the map method) is null.

Here are the steps followed by the expert object:

  1. search a mapper for the given object's class
  2. if no mapper is found the same object is returned
  3. otherwise, call the mapper's Object map(Object) method
When no mapper is found, for XML mappings, the returned value is the object converted to a String using the toString() method.

Once the mapping is delegated to the mapper, the expert loses control but, as the mapper knows its expert object, it should call the expert when it needs to map a type it cannot handle.

If you are curious about how this should be implemented, see the default object to object mapper.

Categorized mapper

Here is a sample using a mapping for a given category:

    expert.map("myCategory", anObject);

The expert object follows next steps:

  1. search a mapper for the given object's class in the given category
  2. if the given category doesn't exist, throw a MappingException
  3. if no mapper is found in the category search a default mapper
  4. if no mapper is found the same object is returned
  5. otherwise, call the mapper's Object map(Object) method

Mapper definition

To create a mapper either define a normal Java class, either use a script that defines the mapping, the scripting language is the mapping definition language.

The same mapping definition can be used for classes with different definitions. Using the mapping definition language has, at some extend, the advantage that you can change the classes and keep the same mapping definitions, see the SimpleXMLMappingTest test class.

Example revisited

Let's create two different mappings for our Hello class, using a lower level approach (see TwoMappings.java).

Register the two mapping definitions

Create an object to XML mapping expert object, and then register the two ways of mapping, name the first "short" and the second "long".

ObjectToXMLMappingExpert expert = new ObjectToXMLMappingExpert();

expert.register("short", Hello.class, 
                "Hello -> hello\n  name @->\n");
expert.register("long", Hello.class, 
                "Hello -> hello\n  name @->\n  message\n");
Here we don't need to declare an alias, for the class type, because we register the definition and the associated type.

Create an object

Hello o = new Hello();

o.setName("MyName");
o.setMessage("Hello, world!");

Apply the mappings...

    System.out.println(expert.map("short", o));
    System.out.println(expert.map("long", o));

The ouput result

	<hello name="MyName">
	</hello>
	
	<hello name="MyName">
	<message><![CDATA[Hello, world!]]></message>
	</hello>	
As you see we don't have any XML header of encoding information, the XML facade we used before adds this information.

Available tests

Facade for object to XML mapping

Mapper search resolution

Simple mappings using mapping definition scripts

Features supported by the mapping definition language

Mapping performance

Resolving access path expressions

EBNF of the mapping definition language

The grammar is presented using the Extended Bachus-Naur Form notation.
The square brackets ([ and ]) present optional parts and the curly brackets ({ and }) present repetitions.
<mapping definition script> ::= [<alias section>] <mapping section>
<alias section>             ::= {<alias definition>} 
<alias definition>          ::= alias <java class name> <alias> eol
<alias>                     ::= <entity name>
<mapping section>           ::= {<mapping definition>}
<mapping definition>        ::= <entity name> [-> <entity name> ] eol 
                                <fields mapping>
<fields mapping>            ::= ident <entity name> 
                                [-> <entity name> [as 'format']] eol
                              | ident <entity name> 
                                [@-> <entity name> [as 'format']] eol
<entity name>               ::= {character | - | .}

Notes

  1. eol is the line break (end of line)
  2. ident is code indentation. Must be at least 1 space character, must be the same on following lines. The tab character is considered as 8 spaces
  3. format must follow either the java.text.DecimalFormat format pattern definition, either the java.text.SimpleDateFormat one.
  4. The entity name is a free sequence of characters, but, as it is used as Java identifier, it must conform to Java identifiers rules.
    The dash character (-) is removed when used to represent Java identifiers and is also used to turn to upper case the character after the dash. For instance hello-world becomes helloWorld or getHelloWorld when used as bean property.
    When the entity name is used as path it can contain several elements, the way the elements are interpreted depends on the context. For instance a.b.3.c is valid if 'b' leads to a list or an array object.

Example

alias net.sf.omap.model.contact.ContactBook ContactBook
alias net.sf.omap.model.contact.Contact Contact
alias net.sf.omap.model.contact.Address Address
alias net.sf.omap.model.contact.Phone Phone

ContactBook -> book
	creation-date -> date as 'yyyyMMdd'
	all-contacts -> contacts

Contact -> contact
	name -> name
	firstName @-> name
	lastName @-> name

Phone -> phone
	number -> number

Address -> address
	name