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 allProjectArgument
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>
<expansionResources>
<expansionResource name="net.democritus.initializer:nsx-application-initializer" version="4.38.0"/>
<expansionResource name="net.democritus.initializer:nsx-expanders-initializer" version="4.38.0"/>
<expansionResource name="net.democritus.initializer:initializer-initializer" version="4.38.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:
- When starting, the initializer application will create an instance of the
Project
model, that is linked to and mirrors perfectly eachProjectType
it finds in the expansion resources listed in theexpansionSettings.xml
file. For eachProjectType
it finds, it will also dynamically create the command-line interface so the--help
is also populated for every availableProjectType
. - If defined, the default values from each
ProjectArgumentType
andProjectFlagType
are copied to theProjectArgument
andProjectFlag
elements. - For each available
ProjectType
, theProject
instance that was created is handed of to each of theProjectDataResolver
classes 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
/--output
can be processed. Because of this,getOutputDirectory()
in theInitializationContext
will 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
/--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 specificProjectType
, over which it iterates and if the arguments are supplied, it updates theProject
model with the supplied values. - Now that the command-line arguments have been processed, all
ProjectDataResolver
classes defined for theProjectType
are called once again to refine theProject
model further. At this stage the output directory is known, sogetOutputDirectory()
in theInitializationContext
will no longer returnnull
. That value is often used to set the default of an argument likeartifactId
to the name of the output directory. - 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. - At this stage the initializer will invoke all
InteractiveModeHandler
classes defined for theProjectType
. These are intended to prompt the user with questions. AnInteractionProvider
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 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 theProject
beyond setting the arguments to values obtained from user-interaction. - The final step of the
Project
model refinement once again calls everyProjectDataResolver
class 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
Project
model being finalized, the initializer will write this to a<project.name>.xml
file, alongside anexpansionSettings.xml
file with the output directory and expansion resources, as well as apom-initializer.xml
Maven file to run the expansion for theProject
. Unless--export
is defined, the initializer will now invoke Maven to run the expansion on thisProject
model to perform the actual initialization.