Skip to main content

Mapping Imports

Many generated artifacts have some form of import or include statements that need to be expanded. This guide explains how to manage imports for some of the standard file types. After that, it explains how to add support for other import strategies.

The syntax used here is introduced in version 2023.1.0 of the mapping xsd, so make sure the xmlns attribute point to this version or one more recent:

<?xml version="1.0" encoding="UTF-8" ?>
<mapping xmlns="https://schemas.normalizedsystems.org/xsd/expanders/2023/1/0/mapping">

Next, we need to add the artifact definition to the head of the mapping file. This definition defines the importStrategy (see below) and the location of the artifact currently being generated.

<mapping xmlns="https://schemas.normalizedsystems.org/xsd/expanders/2023/1/0/mapping">
<artifact this="'{artifact}'" importStrategy="{strategy}"/>
info

The this attribute is an expression, so you will need to use quotes when defining a String value.

Java Imports

For java artifacts, define the importStrategy java and the fully qualified class name of the artifact.

<artifact this="dataElement.qualifiedName + 'Bean'" importStrategy="java"/>

Then, add define classes in uses statements. These statements declare that the defined class is being used and thus needs to be imported.

<uses eval="'jakarta.ejb.Stateless'"/>

You can also define them in a group statement. This can be useful if the imports are only added conditionally.

<group name="importMethod" if="dataElement.getOption('io.import').defined">
<uses eval="'net.democritus.upload.ImportFile'"/>
<uses eval="'net.democritus.upload.ImportResult'"/>
</group>

Or, you can define them in a list statement. This can be useful for classes used by LinkFields or ValueFields.

<list name="linkVariables" eval="dataElement.fields" param="field"
filter="field.linkField neq null"
unique="field.linkField.targetElement">
<value name="class" eval="classBuilder.from(field.linkField.targetElement)"/>
<uses eval="field.linkField.targetElement.qualifiedName + 'Local'"/>
</list>

Finally, add @imports to the template. The classes will be de-duplicated and filtered to include only the necessary imports.

BeanExpander.stg
base() ::= <<
package <packageName>;

@imports

@Stateless
public class <dataElement.name>Bean {}
>>

StarshipBean.java
package net.demo;

import jakarta.ejb.Stateless;
import net.democritus.upload.ImportFile;
import net.democritus.upload.ImportResult;
import net.demo.people.SpacePortLocal;

@Stateless
public class StarshipThingBean {}
Static Java Imports

Static imports can be defined by adding type="static" to the uses statement.

  <uses type="static" eval="'net.democritus.sys.NullDataRef.EMPTY_DATA_REF'"/>

CommonJS/AMD imports

For js artifacts, define the importStrategy amd or commonjs.

<artifact
this="component.name + '/' + dataElement.name.firstToLower
+ '/' + dataElement.name.firstToLower + '-std-edit.js'"
importStrategy="commonjs"/>

AMD dependencies are part of the module header. This is how requirejs modules are intended to be written, but you might run into issues when mixing it with the commonjs notation.

EditDialogExpander.stg
base() ::= <<
@imports

function defineEditDialog() {
}
})
>>






starship-std-edit.js
define([
'require',
'knockout',
'nsx/triggers'
], function (
require,
ko,
Trigger
) {

function defineEditDialog() {
}
})

CommonJS dependencies are imported by calling the require() function.

EditDialogExpander.stg
base() ::= <<
define(function (require) {
@imports

function defineEditDialog() {
}
})
>>
starship-std-edit.js
define(function (require) {
const ko = require('knockout');
const Trigger = require('nsx/triggers');

function defineEditDialog() {
}
})

Define imported artifacts with uses statements.

<!-- Add ` as {var}` to define the library name. -->
<uses eval="'knockout as ko'"/>
<!-- Optionally place `{` and `}` around the module path. -->
<uses eval="'{nsx/triggers} as ko'"/>
<!-- If no variable name is provided, a name is picked based on the filename. -->
<uses eval="'nsx/nsx-application'"/>

Technology-dependent Imports

In some cases, you may need to be able to change imports when switching technologies. E.g. some libraries may change packages with a new version. For this, you can add a TechnologyLexicon. A TechnologyLexicon is linked to a technology and will only be activated if that technology is present in the Application (Program).

It defines a number of CoordinateRedirections, which are used to map usages to the correct coordinate.

<dataResource type="expansionControl::TechnologyLexicon">
<technologyLexicon>
<technology name="JAVA_EE"/>
<coordinateRedirections>
<coordinateRedirection>
<coordinate>jakarta.ejb.EJB</coordinate>
<target>javax.ejb.EJB</target>
</coordinateRedirection>
<coordinateRedirection>
<coordinate>jakarta.ejb.Stateless</coordinate>
<target>javax.ejb.Stateless</target>
</coordinateRedirection>
</coordinateRedirections>
</technologyLexicon>
</dataResource>

Providing new ArtifactImportStrategies

It is possible to provide more import strategy options. You will need to:

  1. Add a DataResource with the new ArtifactImportStrategy
  2. Implement the ArtifactImportResolver interface to process the ArtifactUsages
  3. Implement the conversion to output for the template in an ArtifactImportsDefinition implementation
<dataResource type="expansionControl::ArtifactImportStrategy">
<artifactImportStrategy name="helloWorld">
<implementation>net.democritus.expander.hello.HelloWorldImportResolver</implementation>
</artifactImportStrategy>
</dataResource>

Then, in the artifact definitions, set the importStrategy to the name of the ArtifactImportStrategy.

<?xml version="1.0" encoding="UTF-8" ?>
<mapping xmlns="https://schemas.normalizedsystems.org/xsd/expanders/2023/1/0/mapping">
<artifact this="'Mark'" importStrategy="helloWorld"/>

<uses eval="'Ben'"/>
<uses eval="neighbour"/>
</mapping>

Import Parameters

It is possible to define parameters in an imports anchor:

@imports(param1, param2=value)

These parameters are passed to the ArtifactImportResolver with the setImportsAnchor() method.

This can be used if you need imports in multiple locations in your template. E.g. in Angular Component files, there are imports for other file modules, and imports in the Component declaration.

// file imports
import { Component } from '@angular/core'
import { CommonModule } from '@angular/common'

@Component({
selector: 'my-component',
standalone: true,
imports: [
// component imports
CommonModule
]
})
export class MyComponent {}

The template uses a parameter to keep the imports separate:

base() ::= <<
@imports

@Component({
selector: 'my-component',
standalone: true,
imports: [
@imports(Component)
]
})
export class MyComponent {}
>>

The ArtifactImportResolver then implements the import resolution depending on the provided parameters.

  public ArtifactImportsDefinition resolve(List<ArtifactUsage> usages) {
if (importsAnchor.hasParam("Component")) {
return resolveComponentImports(usages);
} else {
return resolveEsModuleImports(usages);
}
}