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 aProjectFlag.requestString(InteractionRequest<String>): Allows the handler to prompt the user for a string value. This is typically used to prompt for the value of aProjectArgument. This is the only alternative to a boolean value, as allProjectArgumentvalues 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>
<expansionResources>
<expansionResource name="net.democritus.initializer:nsx-application-initializer" version="5.7.0"/>
<expansionResource name="net.democritus.initializer:nsx-expanders-initializer" version="5.7.0"/>
<expansionResource name="net.democritus.initializer:initializer-initializer" version="5.7.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 one of two locations (with the following priority:
- In a subfolder of the user folder:
~/.nsinit/expansionSettings.xml. - 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:
- When starting, the initializer application will create an instance of the
Projectmodel, that is linked to and mirrors perfectly eachProjectTypeit finds in the expansion resources listed in theexpansionSettings.xmlfile. For eachProjectTypeit finds, it will also dynamically create the command-line interface so the--helpis also populated for every availableProjectType. - If defined, the default values from each
ProjectArgumentTypeandProjectFlagTypeare copied to theProjectArgumentandProjectFlagelements. - For each available
ProjectType, theProjectinstance that was created is handed of to each of theProjectDataResolverclasses defined for thatProjectType. 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/--outputcan be processed. Because of this,getOutputDirectory()in theInitializationContextwill returnnull. - 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/--outputargument, defaulting if not defined to the directory where the initializer was invoked. This sub-command contains all the command-line arguments for the specificProjectType, over which it iterates and if the arguments are supplied, it updates theProjectmodel with the supplied values. - Now that the command-line arguments have been processed, all
ProjectDataResolverclasses defined for theProjectTypeare called once again to refine theProjectmodel further. At this stage the output directory is known, sogetOutputDirectory()in theInitializationContextwill no longer returnnull. That value is often used to set the default of an argument likeartifactIdto the name of the output directory. - The initializer will check if the option
-y/--yes-to-allhas been set on command-line. If it has, step 7 and 8 are skipped entirely, if not it will start the interactive mode handling. - At this stage the initializer will invoke all
InteractiveModeHandlerclasses defined for theProjectType. These are intended to prompt the user with questions. AnInteractionProviderobject 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 theProject, 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 theProjectbeyond setting the arguments to values obtained from user-interaction. - The final step of the
Projectmodel refinement once again calls everyProjectDataResolverclass defined for theProjectType. This is a final opportunity to process the values, after the user has had the opportunity to input more data through the interactive mode. - With the entire
Projectmodel being finalized, the initializer will write this to a<project.name>.xmlfile, alongside anexpansionSettings.xmlfile with the output directory and expansion resources, as well as apom-initializer.xmlMaven file to run the expansion for theProject. Unless--exportis defined, the initializer will now invoke Maven to run the expansion on thisProjectmodel to perform the actual initialization.