What it does in BusinessWorks
Builds one document from another: process data in, an activity’s input schema out. Under the
Designer canvas it is XSLT, but what lands in the .process file is the same mechanism every other
activity uses for its input mapping, pd:inputBindings, one <xsl:value-of select="..."> per output
field. A Mapper activity is not structurally different from any other activity’s input mapping; it
just has no engine call beyond passing the shape it built to whatever comes next, and it usually has
more bindings than a typical activity because building a document is its entire job.
The Camel equivalent
<pd:activity name="Build Order Summary">
<pd:type>com.tibco.plugin.mapper
.MapperActivity
</pd:type>
<pd:inputBindings>
<CustomerId>
<xsl:value-of
select="$Order/CustomerId"/>
</CustomerId>
<OrderTotal>
<xsl:value-of
select="$Order/Total"/>
</OrderTotal>
</pd:inputBindings>
</pd:activity>// the whole activity, as emitted
.setProperty("CustomerId",
simple("${exchangeProperty.Order}"))
.setProperty("OrderTotal",
simple("${exchangeProperty.Order}"))
.log(LoggingLevel.TRACE,
"mapper Build Order Summary (inline)")Where the semantics diverge
Only one shape qualifies for inline translation, and it is narrower than it looks. A Mapper
activity is only translated field-by-field when every one of its input bindings is a bare
<xsl:value-of select="$Var/path"/>, there are six bindings or fewer, and none of them is a literal,
a function call, a concatenation or an XPath predicate. One binding outside that shape, or a
seventh binding of any shape, and the entire activity falls back to a single bean call instead,
described below.
Even inside that narrow shape, the source path after the variable is checked but not kept. The
rule validates that path has no :, [ or ( in its first segment, then discards it: the emitted
expression is ${exchangeProperty.<Var>}, the variable name alone. The code sample above is real:
$Order/CustomerId and $Order/Total are two different fields on the same source object, and both
compile to the identical expression. If Order is a property holding a single scalar this is
harmless, because there is only one thing it could mean; if Order is a structured object, every
binding that reads from it collapses onto the whole object, and whichever field the route reads next
determines whether that accidentally still works.
Everything else becomes .bean("<name>Mapper", "map"), and the rule does not write that bean.
No tib: function handling, no XSLT translation, no partial mapping of the bindings that were
simple: the whole activity becomes one call to a bean whose implementation is presumed to exist.
When nothing defines a Spring bean under that name, the generator’s project-wide auto-stub pass
notices the unresolved reference and writes a pass-through stand-in into a shared
CommonStubBeans configuration: body unchanged, one debug log line, guarded by
@ConditionalOnMissingBean so a real @Component registered under the same name quietly takes
over. The route compiles and starts. The mapping does nothing until that bean is written.
Neither outcome is marked. The rule is unconditional and attaches no TODO_HUMAN comment in
either branch, which is also why the coverage report carries this type as translated: correct by
the report’s own definition, an unconditional rule with no marker, but that definition does not
distinguish a one-property copy that happens to be right from a silent no-op stub that happens to
compile.
What the toolchain does
Reads the activity’s pd:inputBindings. If there are six or fewer and every one matches
<xsl:value-of select="$Var/path"/> with a plain, unprefixed path, it emits one setProperty per
binding, target the sanitised output field name, value ${exchangeProperty.<Var>}, followed by a
single trace-level log line. Otherwise it emits one .bean("<activityName>Mapper", "map") call and
leaves the implementation to whatever bean is registered under that name at runtime, auto-stubbed to
a pass-through if nothing else claims it.