NS Initializer Model
The initializer applicationApplicationThe overarching model container grouping all components, domain logic, and configurations for an enterprise solution. 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. This section dives into the elements of the NS metamodel.
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 applicationApplicationThe overarching model container grouping all components, domain logic, and configurations for an enterprise solution..
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 applicationApplicationThe overarching model container grouping all components, domain logic, and configurations for an enterprise solution. 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 resourceExpansion Resource (ExpansionResource). A reusable resource bundle containing expanders and features that provide specific application capabilities, defined in the expansion settings and added to the classpath during expansion., but is processed by the ProjectDataResolver and InteractiveModeHandler classes.
For each ProjectArgumentType and ProjectFlagType found in the ProjectType linked to a Project, the initializer applicationApplicationThe overarching model container grouping all components, domain logic, and configurations for an enterprise solution. 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 fieldFieldA property of a DataElement expanded to a database column and user interface input element., the only other additional fieldFieldA property of a DataElement expanded to a database column and user interface input element. in value, which holds the value of the argument.
The Project element coupled to the InitProject program typeProgram TypeAn architectural style with predefined technologies/standards (e.g., 'JEE Application'). for expansion in the initializer model. This means that unlike typical applicationApplicationThe overarching model container grouping all components, domain logic, and configurations for an enterprise solution. expansion where the program typeProgram TypeAn architectural style with predefined technologies/standards (e.g., 'JEE Application'). Application is expanded, in this model the expandersExpanderA unit of generation that combines a data tree instance (functional M1 model) with a template to generate an artifact. directly expand the Project element. The program typeProgram TypeAn architectural style with predefined technologies/standards (e.g., 'JEE Application'). InitProject itself indicates to the expandersExpanderA unit of generation that combines a data tree instance (functional M1 model) with a template to generate an artifact. that Project is the root element for expansion.
Data resources
The ProjectType model is defined as part of the data resources in the expansion resourceExpansion Resource (ExpansionResource). A reusable resource bundle containing expanders and features that provide specific application capabilities, defined in the expansion settings and added to the classpath during expansion.. This is a set of models that can be added to an expansion resourceExpansion Resource (ExpansionResource). A reusable resource bundle containing expanders and features that provide specific application capabilities, defined in the expansion settings and added to the classpath during expansion., so it can be read out by the expandersExpanderA unit of generation that combines a data tree instance (functional M1 model) with a template to generate an artifact. 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 resourceExpansion Resource (ExpansionResource). A reusable resource bundle containing expanders and features that provide specific application capabilities, defined in the expansion settings and added to the classpath during expansion.. In the case of the initializer expansion resourcesExpansion Resource (ExpansionResource). A reusable resource bundle containing expanders and features that provide specific application capabilities, defined in the expansion settings and added to the classpath during expansion., 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 resourceExpansion Resource (ExpansionResource). A reusable resource bundle containing expanders and features that provide specific application capabilities, defined in the expansion settings and added to the classpath during expansion. is an addon for another initializer expansion resourceExpansion Resource (ExpansionResource). A reusable resource bundle containing expanders and features that provide specific application capabilities, defined in the expansion settings and added to the classpath during expansion. 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 resourceExpansion Resource (ExpansionResource). A reusable resource bundle containing expanders and features that provide specific application capabilities, defined in the expansion settings and added to the classpath during expansion.. It is however recommended to only have one per expansion resourceExpansion Resource (ExpansionResource). A reusable resource bundle containing expanders and features that provide specific application capabilities, defined in the expansion settings and added to the classpath during expansion..
<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 expandersExpanderA unit of generation that combines a data tree instance (functional M1 model) with a template to generate an artifact., just like in any other expander bundleExpander BundleA set of expanders sharing a repository, packaged into an expansion resource or archive.. One notable difference is that the isApplicable fieldFieldA property of a DataElement expanded to a database column and user interface input element. of the expanderExpanderA unit of generation that combines a data tree instance (functional M1 model) with a template to generate an artifact. should always check the ProjectType that is being expanded, as the expandersExpanderA unit of generation that combines a data tree instance (functional M1 model) with a template to generate an artifact. 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
Zero or more ProjectDataResolver classes can be defined for a ProjectType. If present, they 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 systemSystemA set of interconnected components that work together to achieve a specific purpose or function. 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 resourceExpansion Resource (ExpansionResource). A reusable resource bundle containing expanders and features that provide specific application capabilities, defined in the expansion settings and added to the classpath during expansion., 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;
}
}