Context:

This proposal is to add support for

  1. Transforms between different models
  2. Mixins, includes
  3. Well-defined construction rules
  4. Extending of pom with additional meta-data

Solution 

There are two types of developers that will be interested in the project builder: 

  1. Framework Developer: a developer that extends data models, creates new transforms or creates new data models
  2. User: a developer who uses the framework to read models and interpolate values

We will address each separately.

Framework Developer

For new models, the framework developer will need to create a class that implements the ModelTransformer interface. The ModelTransformer.transformToDomainModel instance transforms a specified list of model properties (the canonical data model) into a single domain model. The list may contain a hierarchy (inheritance) of model information. In the case of PomClassicDomainModel, the returned DomainModel instance is a wrapper for the maven model. ModelTransformer.transformToModelProperties instance reverses the process of transforming the domain models to the canonical format of model properties. Thus the framework developer is responsible for doing bi-directional transform between data models and the canonical data model.

DomainModel transformToDomainModel(List<ModelProperty> properties);

List<ModelProperty> transformToModelProperties(List<DomainModel> domainModels);

The second interface that the framework developer is required to implement is the ModelContainer. This is implemented for any part of the model which is a collection.

ModelContainerAction containerAction(ModelContainer modelContainer);
List<ModelProperty> getProperties();

The ModelContainer.containerAction implementation instance is required to return model container action (noop, delete, join) for the specified model container. For example, the following implementation says that if the group id and artifact id are the same but the version is different then delete the passed in container. If the group id, artifact id and version are the same, then join the containers. If the group id or artifact id are different, the don't do anything. 

public ModelContainerAction containerAction(ModelContainer modelContainer) {
  ArtifactManagementModelContainer c = (ArtifactManagementModelContainer) modelContainer;
  if (c.groupId.equals(groupId) && c.artifactId.equals(artifactId)) {
    return (c.version.equals(version)) ? ModelContainerAction.JOIN : ModelContainerAction.DELETE;
  } else {
    return ModelContainerAction.NOP;
  }
 }

The framework developer doesn't need to worry about any other mechanics; just transforming and determining the model action.

User

The user only needs to concern themselves with three classes: DomainModel, ModelTransformer and ModelTransformerContext. The DomainModel and ModelTransformer are implemented by framework developers so the user needs to obtain these instances through whatever means the framework developer has specified. They then use the ModelTransformerContext passing in the transformers and domain models to the ModelTransformerContext.transform method. They will get back the fully processed domain model.

DomainModel transform(List<DomainModel> domainModels, ModelTransformer fromModelTransformer, ModelTransformer toModelTransformer)

In the case of transforming from say pom classic domain model to pom classic domain model, the instance passed in as fromModelTransformer and toModelTransformer parameters will be same. If transforming from say pom class domain model to pom model with attributes domain model, different transformers will be used for each parameter. Thus the user can change the type of returned DomainModel depending on the toModelTransformer instance.

General Sorting

The framework assigns each field within the pom a URI key and a value, which may be null. The following is an example of a model property list where the ordering from parent to child model is: [C, B, A]. All processing is done on this canonical model.

Uri = [http://apache.org/model/project], Value = null
Uri = [http://apache.org/model/project/groupId], Value = org.apache.maven
Uri = [http://apache.org/model/project/artifactId], Value = maven-a
Uri = [http://apache.org/model/project/version], Value = 1.1
Uri = [http://apache.org/model/project/dependencyManagement], Value = null
Uri = [http://apache.org/model/project/dependencyManagement/dependencies#collection], Value = null
Uri = [http://apache.org/model/project/dependencyManagement/dependencies#collection/dependency], Value = null
Uri = [http://apache.org/model/project/dependencyManagement/dependencies#collection/dependency/groupId], Value = org.apache.maven.dep
Uri = [http://apache.org/model/project/dependencyManagement/dependencies#collection/dependency/artifactId], Value = artifact-dep
Uri = [http://apache.org/model/project/dependencyManagement/dependencies#collection/dependency/version], Value = 1.1
Uri = [http://apache.org/model/project/dependencyManagement/dependencies#collection/dependency], Value = null
Uri = [http://apache.org/model/project/dependencyManagement/dependencies#collection/dependency/groupId], Value = org.bogus
Uri = [http://apache.org/model/project/dependencyManagement/dependencies#collection/dependency/artifactId], Value = bogus-dep
Uri = [http://apache.org/model/project/dependencyManagement/dependencies#collection/dependency/version], Value = 1.0
Uri = [http://apache.org/model/project], Value = null
Uri = [http://apache.org/model/project/groupId], Value = org.apache.maven
Uri = [http://apache.org/model/project/artifactId], Value = maven-b
Uri = [http://apache.org/model/project/version], Value = 1.2
Uri = [http://apache.org/model/project/dependencyManagement/dependencies#collection], Value = null
Uri = [http://apache.org/model/project/dependencyManagement/dependencies#collection/dependency], Value = null
Uri = [http://apache.org/model/project/dependencyManagement/dependencies#collection/dependency/groupId], Value = org.apache.maven.dep
Uri = [http://apache.org/model/project/dependencyManagement/dependencies#collection/dependency/artifactId], Value = artifact-dep
Uri = [http://apache.org/model/project/dependencyManagement/dependencies#collection/dependency/version], Value = 1.1
Uri = [http://apache.org/model/project/dependencyManagement/dependencies#collection/dependency/scope], Value = compile
Uri = [http://apache.org/model/project], Value = null
Uri = [http://apache.org/model/project/groupId], Value = org.apache.maven
Uri = [http://apache.org/model/project/artifactId], Value = maven-c
Uri = [http://apache.org/model/project/version], Value = 1.1
Uri = [http://apache.org/model/project/dependencyManagement/dependencies#collection], Value = null
Uri = [http://apache.org/model/project/dependencyManagement/dependencies#collection/dependency], Value = null
Uri = [http://apache.org/model/project/dependencyManagement/dependencies#collection/dependency/groupId], Value = org.apache.maven.dep
Uri = [http://apache.org/model/project/dependencyManagement/dependencies#collection/dependency/artifactId], Value = artifact-dep
Uri = [http://apache.org/model/project/dependencyManagement/dependencies#collection/dependency/version], Value = 1.4

The framework would do an initial sort of the concatenated list, with the following rules:

(1) If there is any duplicate URI that does not contain a #collection, only the first URI will be maintained in the list.
(2) If there is any duplicate URI that ends in collection, only the first URI will be maintained in the list.
(3) If a URI contains a #collection in its direct parent of the URI tag, it will be placed directly after the parent.

In the sort, (3) will reverse the dependency list of collections so that the most general model element will be first under the collection. For example, org.apache.maven.dep:artificat-dep:1.4, which is from pom C, would first in the dependency management collection.

Uri = [http://apache.org/model/project], Value = null
Uri = [http://apache.org/model/project/groupId], Value = org.apache.maven
Uri = [http://apache.org/model/project/artifactId], Value = maven-a
Uri = [http://apache.org/model/project/version], Value = 1.1
Uri = [http://apache.org/model/project/dependencyManagement], Value = null
Uri = [http://apache.org/model/project/dependencyManagement/dependencies#collection], Value = null
Uri = [http://apache.org/model/project/dependencyManagement/dependencies#collection/dependency], Value = null
Uri = [http://apache.org/model/project/dependencyManagement/dependencies#collection/dependency/groupId], Value = org.apache.maven.dep
Uri = [http://apache.org/model/project/dependencyManagement/dependencies#collection/dependency/artifactId], Value = artifact-dep
Uri = [http://apache.org/model/project/dependencyManagement/dependencies#collection/dependency/version], Value = 1.4
Uri = [http://apache.org/model/project/dependencyManagement/dependencies#collection/dependency], Value = null
Uri = [http://apache.org/model/project/dependencyManagement/dependencies#collection/dependency/groupId], Value = org.apache.maven.dep
Uri = [http://apache.org/model/project/dependencyManagement/dependencies#collection/dependency/artifactId], Value = artifact-dep
Uri = [http://apache.org/model/project/dependencyManagement/dependencies#collection/dependency/version], Value = 1.1
Uri = [http://apache.org/model/project/dependencyManagement/dependencies#collection/dependency/scope], Value = compile
Uri = [http://apache.org/model/project/dependencyManagement/dependencies#collection/dependency], Value = null
Uri = [http://apache.org/model/project/dependencyManagement/dependencies#collection/dependency/groupId], Value = org.bogus
Uri = [http://apache.org/model/project/dependencyManagement/dependencies#collection/dependency/artifactId], Value = bogus-dep
Uri = [http://apache.org/model/project/dependencyManagement/dependencies#collection/dependency/version], Value = 1.0
Uri = [http://apache.org/model/project/dependencyManagement/dependencies#collection/dependency], Value = null
Uri = [http://apache.org/model/project/dependencyManagement/dependencies#collection/dependency/groupId], Value = org.apache.maven.dep
Uri = [http://apache.org/model/project/dependencyManagement/dependencies#collection/dependency/artifactId], Value = artifact-dep
Uri = [http://apache.org/model/project/dependencyManagement/dependencies#collection/dependency/version], Value = 1.1


Processing Collections

The ModelDataSource contains the underlying list of ModelProperties (canonical data model). After the initial sort (above), all modifications of the data model go though the ModelDataSource class implementation instance. The ModelDataSource handles deleting and joining of ModelContainers, more specifically the deleting and joining of the ModelProperties that the ModelContainers contain.

/**
 * Provides services for joining, deleting and querying model containers.
 */
public interface ModelDataSource {

    /**
     * Join model properties of the specified container a with the specified container b. Any elements of model container
     * a must take precendence over model container b.
     *
     * @param a model container with precedence
     * @param b model container without precedence
     * @return joined model container
     */
    ModelContainer join(ModelContainer a, ModelContainer b);

    /**
     * Deletes properties of the specified model container from the data source.
     *
     * @param modelContainer the model container that holds the properties to be deleted
     */
    void delete(ModelContainer modelContainer);

    /**
     * Return copy of underlying model properties. No changes in this list will be reflected in the data source.
     *
     * @return copy of underlying model properties
     */
    List<ModelProperty> getModelProperties();

    List<ModelContainer> queryFor(String uri);

    /**
     * Initializes the object with model properties.
     *
     * @param modelProperties the model properties that back the data source
     */
    void init(List<ModelProperty> modelProperties, Collection<ModelContainerFactory> modelContainerFactories);
}

After the general sorting, the framework finds each collection URI (has a #collection in the URI) and obtains the collection's ModelContainers by invoking ModelDataSource.queryFor(uri). The framework then does a comparison using ModelContainer.containerAction to determine whether the ModelContainer should be joined or deleted. The framework then invokes the appropriate actions on the ModelDataSource.

Interpolation

Interpolation comes after sorting and merging the inheritence tree.To interpolate a property, say ${project.artifactId} requires scanning the list of model properties for the URI: http://apache.org/model/project/artifactId and obtaining the value. Thus there is easy mapping between URIs and project properties, without the need of reflection.
(1) Timestamp policy: scan list for values with ${build.timestamp} replacing values
(2) Build property policies:

* ${build.directory}
* ${build.outputDirectory}
* ${build.testOutputDirectory}
* ${build.sourceDirectory}
* ${build.testSourceDirectory}
* ${build.scriptSourceDirectory}
* ${reporting.outputDirectory}
* ${basedir}

First, the values of the respective URIs are resolved. If any of the above property values do not reference another property, then their absolute path is resolved and placed back in as the value. For example:

Uri = http://apache.org/model/project/build/outputDirectory, Value = target

Would resolve to:

Uri = http://apache.org/model/project/build/outputDirectory, Value = C:\project\moduleA\target

If the property references another property then the other property is resolved first.

(3) UserProperty (command line values) policy: scan list for URIs that map to user property values.
(4) Environmental policy: scan list for values containing  ${env.*} and replacing them
(5) Model Property policy: scan list for any remaining values containing ${project.*} and replacing them
(6) Illegal values policies: scan list for any illegal values (self-referential)
(7) System property policy

Rules

 There are three primary types of rules, processed in the following order 

1) Project Model Specific Rules - rules that are specific to the pom that are not collection specific, such as constructing of SCM URL, not inheriting packaging, etc

2) General Sorting - sorting rules done during the initial collapse of the XML hierarchy

3) ModelContainer Rules - rules specific to the pom that are done per collection, plugins, resources, etc, These rules have three possible actions: Delete, join or no op.

Project Model Specific Rules
  1. Scm URL (scmURL, scmDeveloperConnection, scmConnection)
    Say most specialized model is M0. Take first occurrence of scm URL from most specialized model, call this model M1. If M0=M1, then scmURL(M0) is used unaltered. If M1 > M0, then scmURL=scmURL(M1) + ArtifactID(M0). This rule is recursive.
  2. Modules are not inherited
  3. If group ID does not exist in pom, use parent group ID if it exists. Otherwise, fail.
  4. If version does not exist in pom, use parent version if it exists. Otherwise, fail.
  5. If plugin execution 'inherited' value is false, do not inherit the plugin execution
  6. If plugin inherited tag is false, do not inherit the plugin
  7. Dependencies must be ordered with the most specialized pom's dependencies appearing first.
  8. project/name property is not inherited
  9. packaging property is not inherited
  10. build/resources node is not inherited
  11. build/TestResources node is not inherited
  12. Profiles node is not inherited
  13. Dependencies with the same groupId, artifactId and version are not joined if they have different types.
  14. Plugin Repositories are not inherited

All exception rules are processed, on a per project model basis, before general sorting and ModelContainer rules (joining and deleting).

General Sorting Rules:

(1) If it is a singleton URI (one per model) with a value, then the most specialized model value will be used.

(2) If it is a singleton URI node with no value and does not contain #collection in the URI, children will be joined. (A v B)/(A & B). If there are multiple values in the inheritence, the operation is applied from most specialized to least specialized model, meaning most specialized values take precedence.

(3) If it is a singleton URI node with no value but containing #collection in the URI, all values will initially be joined: (A v B). ModelContainer rules may be applied to handle duplicates.

There are 43 collections within the pom model that are sorted according to the general collection rules

http://apache.org/maven/project/build/resources#collection
http://apache.org/maven/project/build/testResources#collection
http://apache.org/maven/project/build/extensions#collection
http://apache.org/maven/project/build/plugins#collection
http://apache.org/maven/project/build/plugins/plugin/dependencies#collection
http://apache.org/maven/project/build/plugins/plugin/dependencies/dependency/exclusions#collection
http://apache.org/maven/project/build/plugins/plugin/executions#collection
http://apache.org/maven/project/build/pluginManagement/plugins#collection
http://apache.org/maven/project/build/pluginManagement/plugins/plugin/dependencies/dependency/exclusions#collection
http://apache.org/maven/project/build/pluginManagement/plugins/plugin/executions#collection
http://apache.org/maven/project/build/pluginManagement/plugins/plugin/dependencies#collection
http://apache.org/maven/project/dependencyManagement/dependencies#collection
http://apache.org/maven/project/dependencyManagement/dependencies/dependency/exclusions#collection
http://apache.org/maven/project/dependencies/dependency/exclusions#collection
http://apache.org/maven/project/dependencies#collection
http://apache.org/maven/project/pluginRepositories#collection
http://apache.org/maven/project/repositories#collection
http://apache.org/maven/project/licenses#collection
http://apache.org/maven/project/reporting/plugins#collection
http://apache.org/maven/project/reporting/plugins/plugin/reportSets#collection
http://apache.org/maven/project/contributors#collection
http://apache.org/maven/project/developers#collection
http://apache.org/maven/project/mailingLists#collection
http://apache.org/maven/project/ciManagement/notifiers#collection
http://apache.org/maven/project/profiles#collection
http://apache.org/maven/project/profiles/profile/build/pluginManagement/plugins#collection
http://apache.org/maven/project/profiles/profile/build/plugins/plugin/dependencies/dependency/exclusions#collection
http://apache.org/maven/project/profiles/profile/reporting/plugins#collection
http://apache.org/maven/project/profiles/profile/build/testResources#collection
http://apache.org/maven/project/profiles/profile/build/pluginManagement/plugins/plugin/dependencies#collection
http://apache.org/maven/project/profiles/profile/build/resources#collection
http://apache.org/maven/project/profiles/profile/build/pluginManagement/plugins/plugin/dependencies/dependency/exclusions#collection
http://apache.org/maven/project/profiles/profile/build/plugins/plugin/dependencies#collection
http://apache.org/maven/project/profiles/profile/pluginRepositories#collection
http://apache.org/maven/project/profiles/profile/build/pluginManagement/plugins/plugin/executions#collection
http://apache.org/maven/project/profiles/profile/dependencies#collection
http://apache.org/maven/project/profiles/profile/dependencyManagement/dependencies#collection
http://apache.org/maven/project/profiles/profile/repositories#collection
http://apache.org/maven/project/profiles/profile/reporting/plugins/plugin/reportSets#collection
http://apache.org/maven/project/profiles/profile/build/plugins#collection
http://apache.org/maven/project/profiles/profile/build/plugins/plugin/executions#collection
http://apache.org/maven/project/profiles/profile/dependencies/dependency/exclusions#collection
http://apache.org/maven/project/profiles/profile/dependencyManagement/dependencies/dependency/exclusions#collection
Model Container Rules


Each collection in the model may need a ModelContainer implementation. The following rules apply:

  1. project/build/resources#collection
    No joins, just add additional resource tags. No container needed.
  2. project/build/testResources#collection
    No joins, just add additional testResource tags. No container needed.
  3. project/build/plugins#collection
    If groupId, artifactId and version are equals then join. If only groupId, artifactId are the same, but version is different, delete last container.
  4. project/build/plugins/plugin/dependencies#collection
    If groupId, artifactId and version are equals then join. If only groupId, artifactId are the same, but version is different, delete last container.
  5. project/build/plugins/plugin/dependencies/dependency/exclusions#collection
    No joins, just add additional exclusion tags. No container needed.
  6. project/build/plugins/plugin/executions#collection
    Joined if ids are the same, otherwise additional execution tag is added.
  7. project/build/extensions#collection
    No joins, add extension provided any of artifactId, groupId and version are different.
  8. project/build/pluginManagement/plugins#collection
    If groupId, artifactId and version are equals then join. If only groupId, artifactId are the same, but version is different, delete last container.
  9. project/build/pluginManagement/plugins/plugin/dependencies/dependency/exclusions#collection
    No joins, just add additional exclusion tags. No container needed.
  10. project/build/pluginManagement/plugins/plugin/executions#collection
    Joined if ids are the same, otherwise additional execution tag is added.
  11. project/build/pluginManagement/plugins/plugin/dependencies#collection
    If groupId, artifactId and version are equals then join. If only groupId, artifactId are the same, but version is different, delete last container.
  12. project/dependencyManagement/dependencies#collection
    If groupId, artifactId and version are equals then join. If only groupId, artifactId are the same, but version is different, delete last container.
  13. dependencyManagement/dependencies/dependency/exclusions#collection
    No joins, just add additional exclusion tags. No container needed
  14. project/dependencies/dependency/exclusions#collection
    No joins, just add additional exclusion tags. No container needed
  15. project/dependencies#collection
    If groupId, artifactId and version are equals then join. If only groupId, artifactId are the same, but version is different, delete last container.
  16. project/pluginRepositories#collection
    Joined if ids are the same, otherwise additional pluginRepository tag is added.
  17. project/repositories#collection
    Joined if ids are the same, otherwise additional repository tag is added.
  18. project/licenses#collection
    No joins, just add additional license tags. No container needed
  19. project/reporting/plugins#collection
    If groupId, artifactId and version are equals then join. If only groupId, artifactId are the same, but version is different, delete last container.
  20. project/reporting/plugins/plugin/reportSets#collection
    Joined if ids are the same, otherwise additional reportSet tag is added.
  21. project/contributors#collection
    No joins, just add additional contributor tags. No container needed
  22. project/developers#collection
    No joins, just add additional developer tags. No container needed
  23. project/mailingLists#collection
    No joins, just add additional mailingList tags. No container needed
  24. project/ciManagement/notifiers#collection
    No joins, just add additional notifier tags. No container needed
  25. project/profiles#collection
    Joined if ids are the same, otherwise additional profile tag is added.

There is also a concept of an XML defined collection that can be used to define collections within the configuration sections of the pom. This can be done by adding the property combine.children="append" to an XML element. The framework will then merge the XML elements of parent and child.

All other profile collections/containers are processed according to their respective rules above.

Misc Rules

1. All models inherit from the super pom.

Rules in Question

1. Remove PluginRepositories node: in 2.0.x this node is inherited, in 2.1.x it is not.

Changes between 2.0.x and 2.1
  1. Default group id in pom is no longer supported
  2. SCM URL no longer uses module value but rather the artifact id.
Transforming Between Models

Transforming between XML based models involves changing the URIs. For example to change between the canonical model (which is based on the pom) and a C# project file, you would rename the URIs.

Uri = [http://apache.org/model/project], Value = null
Uri = [http://apache.org/model/project/groupId], Value = org.apache.maven
Uri = [http://apache.org/model/project/artifactId], Value = maven-a
Uri = [http://apache.org/model/project/version], Value = 1.1
Uri = [http://apache.org/model/project/dependencies#collection/dependency], Value = null
Uri = [http://apache.org/model/project/dependencies#collection/dependency/groupId], Value = org.apache.maven.dep
Uri = [http://apache.org/model/project/dependencies#collection/dependency/artifactId], Value = artifact-dep
Uri = [http://apache.org/model/project/dependencies#collection/dependency/version], Value = 1.4
Uri = [http://apache.org/model/project], Value = null
Uri = [http://apache.org/model/Project#collection/PropertyGroup], Value = null
Uri = [http://apache.org/model/Project#collection/PropertyGroup/AssemblyName], Value = maven-a
Uri = [http://apache.org/model/Project#collection/ItemGroup#collection/Reference], Value = null
Uri = [http://apache.org/model/Project#collection/ItemGroup#collection/Reference/Include], Value = artifact-dep

You can then have a standard marshaller that knows how to handle mapping of URIs to XML tags do the output of the C# project file. Since we are using a canonical data model, we can also do transforms between any models. For example, if say there exists bidirectional transforms from

(1) Model A to canonical model

(2) Model B to canonical model

and then I add my own

(3) model C to canonical model

then I can do transforms A<>C, B<>C, even though I know nothing about A and B models.

Extending Models

You can easily add information to the canonical model by defining new URIs. For example, we could introduce toolchains:

Uri = [http://apache.org/model/project], Value = null
Uri = [http://apache.org/model/project/groupId], Value = org.apache.maven
Uri = [http://apache.org/model/project/artifactId], Value = maven-a
Uri = [http://apache.org/model/project/version], Value = 1.1
Uri = [http://apache.org/model/project/toolchain], Value = null
Uri = [http://apache.org/model/project/toolchain/type], Value = jdk
Uri = [http://apache.org/model/project/toolchain/provides], Value = null
Uri = [http://apache.org/model/project/toolchain/provides/jdkVersion], Value = 1.5

Since the standard pom model transformer doesn't recognize the toolchain URI, it ignores it, keeping compatibility. However, an extended pom model implementation could read the toolchain info and expose it to the application.

Versioning Models

Versioning can be handled between the canonical models by introducing a version with the URIs:

Uri = http://apache.org/model/project/v1

The would allow the framework to easily detect the model and handle the URIs differently.

Extending This Framework

This existing framework is general enough to support other canonical data models (like the assembly descriptor). All the sorting and model data source operations are the same. Supporting new models with a new canonical data format requires news ModelContainer and new ModelTransformer implementations implementations that know how to process the format.