Skip to main content

Technical Reference

Preface

The initializer has many similarities to the expanders, in that it generates some files and performs some actions based on a set of predefined arguments. Because of this, the initializer was upgraded to v2.x by replacing the templating system with actual expanders that were executed internally in the initializer at runtime. This system made extensive use of the expander infrastructure, but also limited itself to a very specific subset of capabilities.

Version 3.x addresses this by completely migrating the initialization process into expander bundles, making it fully use the expander infrastructure. What remains of the initializer application itself, is that it facilitates the input of arguments that are needed to run the initialization itself.

Initializer expansion bundles

The initializer expansion bundles are in essence the same as any other expansion resources, with the exception that they contains data resources which represent the project type in a model, as well as some Java classes for dynamic behavior.

Model

The initializer application itself is no longer needed for the initialization process, since it is just a regular expansion now. What is required to enable this is an NS model which describes all the arguments that are needed to initialize the project.

ProjectType

The ProjectType element in the initializer meta-model is an element which describes all the arguments that are required to generate the project. These are used to dynamically generate the command-line interface in the initializer application.

The ProjectType defines the name and potentially an alias for the project type. These are used on the command-line interface to select the appropriate dynamically generated subcommand. E.g.: nsinit <name> ... or nsinit <alias> ....

All arguments are defined as linked ProjectArgumentType and ProjectFlagType elements, which define arguments with a value or a simple boolean flag respectively. Each of these arguments or flags have a name and alias of their own, which is translated to kebab-case on command-line. E.g.: For a flag testFlag, the command-line parameter would be --test-flag. For an argument someValue, the command-line parameter would be --some-value <value>.

In the ProjectType element, it is possible to add ProjectDataResolver elements, which contains the fully-qualified class name of a class which can manipulate the Project model which contains the actual values associated with the type definitions. The goal of this is to provide dynamic behavior to set the values of particular arguments.

The flags and arguments can be set to implicit (default: false), which means that they will not be added as parameters on the command-line interface. These will be assigned the defaultValue initially and can also be manipulated with the ProjectDataResolver class.

The InteractiveModeHandler elements which can be added to the ProjectType contains the fully-qualified class name of the interactive mode handler, which is used to implement the interactive mode of the command-line interface for that project.

Project

The Project model represents an instance of ProjectType and is intended to couple values to the types, which are then used to generate the actual project. The initializer application uses the command-line interface, interactive mode and some refinement steps to produce a Project model, which has a value associated for every argument and flag defines in the ProjectType model it is based on. This element is never part of the expansion resource, but is processed by the ProjectDataResolver and InteractiveModeHandler classes.

For each ProjectArgumentType and ProjectFlagType found in the ProjectType linked to a Project, the initializer application will create a ProjectArgument and ProjectFlag respectively. Their name (as well as that of Project), is assigned the name of the type element. Other than the name field, the only other additional field in value, which holds the value of the argument.

The Project element coupled to the InitProject program type for expansion in the initializer model. This means that unlike typical application expansion where the program type Application is expanded, in this model the expanders directly expand the Project element. The program type InitProject itself indicates to the expanders that Project is the root element for expansion.

Data resources

The ProjectType model is defined as part of the data resources in the expansion resource. This is a set of models that can be added to an expansion resource, so it can be read out by the expanders ahead of the actual expansion.

The data resources are typically defined in a _data.xml file and list all models and their path in the expansion resource. In the case of the initializer expansion resources, this will always at least contains a reference to the file which contains the ProjectType model. The only exception when this is not the case, is when the expansion resource is an addon for another initializer expansion resource that already defines the ProjectType model and does not require any additions.

<dataResources>
<dataResource>
<path>data/initializer/projectTypes.xml</path>
<elementTypeCanonicalName>net.democritus.initializer.model.ProjectType</elementTypeCanonicalName>
</dataResource>
</dataResources>

The projectTypes.xml data resource can technically contain definitions for more than one ProjectType model, which means that it is possible to generate more than one ProjectType with a single expansion resource. It is however recommended to only have one per expansion resource.

<projectTypes>
<projectType name="bestProjectType">
<description>Initialize the best project.</description>
<projectDataResolvers>
<projectDataResolver name="com.example.template.myinit.MyProjectDataResolver"/>
</projectDataResolvers>
<interactiveModeHandlers>
<interactiveModeHandler name="com.example.template.myinit.MyInteractiveModeHandler"/>
</interactiveModeHandlers>
<arguments>
<argument name="groupId">
<description>The groupId that will be used in the POM files.</description>
<alias>groupid</alias>
<default>com.example</default>
</argument>
<argument name="artifactId">
<description>The artifact identifier that will be used in the POM files.</description>
</argument>
</arguments>
<flags>
<flag name="git">
<description>If enabled, no git repository is initialized.</description>
<default value="true"/>
</flag>
<flag name="addKittens">
<description>If enabled, kittens will be added. This is not on command-line as it is implicit.</description>
<default value="true"/>
<implicit value="true"/>
</flag>
</flags>
</projectType>
</projectTypes>

Expanders

Files that should be initialized are created as expanders, just like in any other expander bundle. One notable difference is that the isApplicable field of the expander should always check the ProjectType that is being expanded, as the expanders for all available project types will be processed for any Project, regardless of its type.

<expander xmlns="http://nsx.normalizedsystems.org/20204/expander" name="SomeFileExpander">
<packageName>net.democritus.initializer.template.myinitializer</packageName>
<layerType name="ROOT"/>
<technology name="COMMON"/>
<sourceType name="SRC"/>
<elementTypeName>Project</elementTypeName>
<artifactModifiers/>
<artifactName>SomeFile.ext</artifactName>
<artifactPath>$expansionRoot.directory$</artifactPath>
<isApplicable>project.projectType.name eq 'myProjectType'</isApplicable>
<phase>expansion</phase>
<active value="true"/>
<anchors/>
<customAnchors/>
</expander>

ProjectDataResolver

One or more ProjectDataResolver classes can be defined for a ProjectType and will be called sequentially for various steps in the initialization process. The goal of the ProjectDataResolver class, is to allow initializers to implement dynamic behavior for setting default values and/or updating values provided by the user. This system is also required to set arguments and flags marked as implicit, since the user can only provide the values for these if they use the -D argument, or if an InteractiveModeHandler provides a prompt which sets the value.

As shown in the example below, a typical use-case is setting the artifactId for a Maven project to the name of the output directory as the default value. Note that the output directory is not yet known in the first step of the initialization process, so it is required to verify that it is present, as this class is called multiple times to refine the Project further. Though the phase of the initialization process is not known in this class, the implemented logic should consistently update the Project model based on its current contents whenever the class is called.

public class MyProjectDataResolver implements ProjectDataResolver {

@Override
public ProjectTree resolve(ParameterContext<ProjectTree> parameterContext) {
final Context context = parameterContext.getContext();
final ProjectTree projectTree = parameterContext.getValue();
final InitializationContext initializationContext = context.getContext(InitializationContext.class).getValue();

final File outputDirectory = initializationContext.getOutputDirectory();
// If the output directory has already been determined
if (outputDirectory != null) {
// Find the argument with the name artifactId if it is still empty
findProjectArgument(projectTree, argumentName("artifactId").and(argumentEmpty())).ifPresent(mainArg ->
// And assign it the name of the output directory as its value
mainArg.setValue(outputDirectory.getName()));
}

return projectTree;
}

}

InteractiveModeHandler

The InteractiveModeHandler handler class is used to define the interaction with the user during the interactive mode. This is provided by the expansion resource, as it allows for the most flexible way of implementing the interactive mode. An InteractionProvider object is provided, which has several methods that can facilitate communication with the user during the interactive mode phase:

  • message(InteractionMessage): Allows the handler to simply output any message to the user.
  • requestBoolean(InteractionRequest<Boolean>): Allows the handler to prompt the user with a yes/no question to retrieve a boolean value. This is typically used to prompt for the value of a ProjectFlag.
  • requestString(InteractionRequest<String>): Allows the handler to prompt the user for a string value. This is typically used to prompt for the value of a ProjectArgument. This is the only alternative to a boolean value, as all ProjectArgument values are strings.

The InteractionRequest class can take both a prompt (question) for the user, as well as a defaultValue, which is typically set to the current value of a ProjectArgument or ProjectFlag. It is advised to always set a defaultValue, but if it is not set, the user will be required to fill in a value to proceed.

The ProjectDataResolver classes will be called once more after the InteractiveModeHandler classes, so any post-processing of the data should happen in the ProjectDataResolver. The InteractiveModeHandler should simply store the data in the Project.

public class MyProjectInteractiveModeHandler implements InteractiveModeHandler {

@Override
public ProjectTree execute(InteractionProvider interactionProvider, ProjectTree projectTree) {
// Find the argument artifactId
findProjectArgument(projectTree, argumentName("artifactId")).ifPresent(arg ->
// Set the argument to the value returned by the user (or the default)
arg.setValue(interactionProvider.requestString(new InteractionRequest<String>()
// The question to ask the user
.setPrompt("Specify the artifactId for the project")
.setDefaultValue(arg.getValue()))
.getValue()));

return projectTree;
}

}

ExpansionSettings file

The initializer application operates on a set expansion resources which are set in an expansionSettings.xml file:

expansionSettings.xml
<expansionSettings>
<expansionResources>
<expansionResource name="net.democritus.initializer:nsx-application-initializer" version="4.34.0"/>
<expansionResource name="net.democritus.initializer:nsx-expanders-initializer" version="4.34.0"/>
<expansionResource name="net.democritus.initializer:initializer-initializer" version="4.34.0"/>
</expansionResources>
</expansionSettings>

This file is identically structured as any other file with an ExpansionSettings model, but it only contains expansionResource data refs of all bundles with initializer projects which the initializer should load and process.

By default, the initializer comes with an internal file containing predefined initializer bundles that it will use. It is possible to override this with a different expansionSettings.xml file, by placing it in the same directory as the initializer jar file.

Initialization process

The initializer application goes through various phases in order to create the Project model.

The steps as shown in the diagram are as follows:

  1. When starting, the initializer application will create an instance of the Project model, that is linked to and mirrors perfectly each ProjectType it finds in the expansion resources listed in the expansionSettings.xml file. For each ProjectType it finds, it will also dynamically create the command-line interface so the --help is also populated for every available ProjectType.
  2. If defined, the default values from each ProjectArgumentType and ProjectFlagType are copied to the ProjectArgument and ProjectFlag elements.
  3. For each available ProjectType, the Project instance that was created is handed of to each of the ProjectDataResolver classes defined for that ProjectType. The class can then set some initial values dynamically if needed. At this stage the CLI processor has not yet been called, which means that the output directory has not yet been processed, as this is done only at the point when -o/--output can be processed. Because of this, getOutputDirectory() in the InitializationContext will return null.
  4. The command-line processor is invoked, which then selects the correct command-line interface (sub-command) for the requested ProjectType. The sub-command will first process the -o/--output argument, defaulting if not defined to the directory where the initializer was invoked. This sub-command contains all the command-line arguments for the specific ProjectType, over which it iterates and if the arguments are supplied, it updates the Project model with the supplied values.
  5. Now that the command-line arguments have been processed, all ProjectDataResolver classes defined for the ProjectType are called once again to refine the Project model further. At this stage the output directory is known, so getOutputDirectory() in the InitializationContext will no longer return null. That value is often used to set the default of an argument like artifactId to the name of the output directory.
  6. The initializer will check if the option -y/--yes-to-all has been set on command-line. If it has, step 7 and 8 are skipped entirely, if not it will start the interactive mode handling.
  7. At this stage the initializer will invoke all InteractiveModeHandler classes defined for the ProjectType. These are intended to prompt the user with questions. An InteractionProvider object is provided to communicate with the user at this stage. For each prompt it is possible to define a default value. These are typically set to the current value of an argument in the Project, so the user can accept that current value or override it with a new value of their own choosing. It is not intended for this stage to actually do any processing of the Project beyond setting the arguments to values obtained from user-interaction.
  8. The final step of the Project model refinement once again calls every ProjectDataResolver class defined for the ProjectType. This is a final opportunity to process the values, after the user has had the opportunity to input more data through the interactive mode.
  9. With the entire Project model being finalized, the initializer will write this to a <project.name>.xml file, alongside an expansionSettings.xml file with the output directory and expansion resources, as well as a pom-initializer.xml Maven file to run the expansion for the Project. Unless --export is defined, the initializer will now invoke Maven to run the expansion on this Project model to perform the actual initialization.