News and Notes from the Makers of Nexus | Sonatype Blog

General Framework For Model Inheritance

Written by Shane Isbell | November 10, 2008

Maven 3.0 uses a new standalone component that handles inheritance and interpolation of a model in any format. The model needn't even be XML based. If you can translate your model into a list of property-value pairs, you can use this framework for inheritance.

ModelProperties are the building blocks and consist of a URI and a value. If you want to create a hierarchy (or a node), you would specify something like:

mp("http://myorg.org/model/foo", null)
mp("http://myorg.org/model/foo/bar", "fooey")

In XML, this would correspond to:

  <foo>
    <bar>fooey</bar>
  </foo>

If two of the same properties are stacked, say "http://myorg.org/model/foo/bar", then the top most property value will be used. This allows for a simple linearized inheritance, where the most specialized model overrides values of its parent model. But what happens to collections, like dependencies, that need to be merged as we move up the hierarchy of parent poms?

That's where the concept of the ModelContainer comes in. The first thing to do is to specify #collection in the URI: http://apache.org/maven/project/dependencies#collection.

This tells the ModelTransformer implementation instance not to override the property, as it is part of a collection. Instead, the framework just to adds the property to the general collection. Obviously, the framework doesn't know anything about how to handle the specifics of these URI collections, as that is a model specific rule. Thus the framework must delegate the decision, calling all the ModelContainerFactories that is has been initialized with, asking each if they can provide a container to handle the property.

 ModelDataSource source = new DefaultModelDataSource();
 source.init( props, Arrays.asList( new ArtifactModelContainerFactory(), new IdModelContainerFactory() ) );

A ModelContainer implementation is rather simple. The framework just passes in all like model containers and asks what it should do. A ModelContainer is required to implement the following method:

  ModelContainerAction containerAction( ModelContainer modelContainer );

A ModelContainer implementation instance will know its own model properties (say version, groupId and artifactId) and decides what action to take in relation to the specified modelContainer, which is passed to it by the framework. The returned ModelContainerAction is an enum that specifies an action: no operation, delete or join of containers.

For example, if either the groupIds or versions are different, the container would return a NOP, telling maven-shared-model to leave the specified container in the collection. If the artifactIds, versions and groupIds are all the same, then the container would tell the framework to join the containers. That's where the implementation of ModelDataSource comes in. It has join, delete and query methods for handling the operations on ModelContainers. If the ModelContainer implementation told the framework to join model containers, the framework would then take both model containers and invoke: ModelDataSource.join(ModelContainer a, ModelContainer b)

Out of the box, you can use the ModelTransformer for transforming XML to a list of model properties, but nothing would stop someone from plugging in their own transformer for virtually any other format. Thus you can used maven-shared-model for inheriting any data format according to a general rule set that you can customize.

Clearly the component name, maven-shared-model, is not appropriate, as it can be used independently of Maven and does not dictate the use of the Maven model. After the first alpha release of Maven 3.0, I'll be creating a separate project, independent of trunk, for maven-shared-model.