This is an automated email from the ASF dual-hosted git repository.

asf-gitbox-commits pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/ant-antlibs-cyclonedx.git


The following commit(s) were added to refs/heads/main by this push:
     new 61e4d8b  refactor sbomLink and ivyModule into a reusable componentdef
61e4d8b is described below

commit 61e4d8b2d942b7890ed7d42eb2fb4073f5e19f55
Author: Stefan Bodewig <[email protected]>
AuthorDate: Sun Aug 16 16:52:47 2026 +0200

    refactor sbomLink and ivyModule into a reusable componentdef
---
 build.xml                                          |  74 ++---
 changes.xml                                        |  11 +-
 docs/component.html                                | 301 +--------------------
 docs/componentbom.html                             |   8 +-
 docs/ivyModule.html                                | 246 +++++++++++++++++
 docs/sbomLink.html                                 |  98 +++++++
 example/ant-cyclonedx-0.2alpha.cdx.diff            |   4 +-
 example/ant-cyclonedx-0.2alpha.cdx.json            |  32 +--
 example/ant-cyclonedx-0.2alpha.ivy.cdx.json        |  32 +--
 src/main/org/apache/ant/cyclonedx/Component.java   | 217 +--------------
 .../apache/ant/cyclonedx/ComponentResolver.java    |  41 +++
 .../ant/cyclonedx/IvyModuleComponentResolver.java  | 136 ++++++++--
 .../ant/cyclonedx/SbomLinkComponentResolver.java   |  35 ++-
 src/main/org/apache/ant/cyclonedx/antlib.xml       |   9 +
 src/tests/antunit/component-ivymodule-test.xml     |  20 +-
 src/tests/antunit/component-sbomlink-test.xml      |  28 +-
 src/tests/antunit/component-test.xml               |  10 +-
 17 files changed, 662 insertions(+), 640 deletions(-)

diff --git a/build.xml b/build.xml
index c6fc8b8..b439683 100644
--- a/build.xml
+++ b/build.xml
@@ -100,10 +100,10 @@ under the License.
 
   <target name="define-cyclonedx-components"
           depends="define-core-ant-components"
-          if="can.use.cyclonedx">
+          if="can.use.cyclonedx"
+          xmlns:cdx="antlib:org.apache.ant.cyclonedx">
     <cdx:externalreferenceset
-        id="antlib-ext-refs"
-        xmlns:cdx="antlib:org.apache.ant.cyclonedx">
+        id="antlib-ext-refs">
       <externalReference
           type="VCS"
           url="https://gitbox.apache.org/repos/asf/ant-antlibs-cyclonedx.git"/>
@@ -118,7 +118,8 @@ under the License.
 
   <target name="create-antlib-sbom-ivy"
           depends="define-cyclonedx-components"
-          if="can.use.cyclonedx">
+          if="can.use.cyclonedx"
+          xmlns:cdx="antlib:org.apache.ant.cyclonedx">
     <uptodate property="antlib-sbom-ok"
               targetfile="${build.lib}/${artifact.stub}.ivy.cdx.json">
       <srcresources>
@@ -134,13 +135,12 @@ under the License.
         useComponentSupplier="true"
         useComponentManufacturer="true"
         unless:set="antlib-sbom-ok"
-        xmlns:unless="ant:unless"
-        xmlns:cdx="antlib:org.apache.ant.cyclonedx">
+        xmlns:unless="ant:unless">
       <component
           description="Apache CycloneDX Antlib"
           publisher="The Apache Software Foundation"
           supplierIsManufacturer="true">
-        <ivymodule conf="default,provided" externalConf="provided">
+        <cdx:ivyModule conf="default,provided" externalConf="provided">
           <templateComponent refid="ant"/>
           <templateComponent refid="ant-launcher"/>
           <templateComponent
@@ -171,7 +171,7 @@ under the License.
                 type="ADVISORIES"
                 url="https://ant.apache.org/ivy/security.html"/>
           </templateComponent>
-        </ivymodule>
+        </cdx:ivyModule>
         <file file="${jarname}"/>
         <supplier refid="ant-pmc"/>
         <license refid="apache-2"/>
@@ -185,7 +185,8 @@ under the License.
 
   <target name="create-antlib-sbom"
           depends="define-cyclonedx-components"
-          if="can.use.cyclonedx">
+          if="can.use.cyclonedx"
+          xmlns:cdx="antlib:org.apache.ant.cyclonedx">
     <uptodate property="antlib-sbom-ok"
               targetfile="${build.lib}/${artifact.stub}.cdx.json">
       <srcresources>
@@ -201,8 +202,7 @@ under the License.
         useComponentSupplier="true"
         useComponentManufacturer="true"
         unless:set="antlib-sbom-ok"
-        xmlns:unless="ant:unless"
-        xmlns:cdx="antlib:org.apache.ant.cyclonedx">
+        xmlns:unless="ant:unless">
       <component
           name="${artifact.name}"
           group="org.apache.ant"
@@ -257,27 +257,27 @@ under the License.
         <dependency componentRef="json-schema-validator"/>
       </additionalComponent>
       <additionalComponent id="commons-codec">
-        <sbomLink>
+        <cdx:sbomLink>
           <url 
url="https://repo1.maven.org/maven2/commons-codec/commons-codec/1.22.1/commons-codec-1.22.1-cyclonedx.json"/>
-        </sbomLink>
+        </cdx:sbomLink>
         <file file="${lib.dir.compile}/commons-codec-1.22.1.jar"/>
       </additionalComponent>
       <additionalComponent id="commons-io">
-        <sbomLink>
+        <cdx:sbomLink>
           <url 
url="https://repo1.maven.org/maven2/commons-io/commons-io/2.22.0/commons-io-2.22.0-cyclonedx.json"/>
-        </sbomLink>
+        </cdx:sbomLink>
         <file file="${lib.dir.compile}/commons-io-2.22.0.jar"/>
       </additionalComponent>
       <additionalComponent id="commons-lang3">
-        <sbomLink>
+        <cdx:sbomLink>
           <url 
url="https://repo1.maven.org/maven2/org/apache/commons/commons-lang3/3.20.0/commons-lang3-3.20.0-cyclonedx.json"/>
-        </sbomLink>
+        </cdx:sbomLink>
         <file file="${lib.dir.compile}/commons-lang3-3.20.0.jar"/>
       </additionalComponent>
       <additionalComponent id="commons-collections4">
-        <sbomLink>
+        <cdx:sbomLink>
           <url 
url="https://repo1.maven.org/maven2/org/apache/commons/commons-collections4/4.5.0/commons-collections4-4.5.0-cyclonedx.json"/>
-        </sbomLink>
+        </cdx:sbomLink>
         <file file="${lib.dir.compile}/commons-collections4-4.5.0.jar"/>
       </additionalComponent>
       <additionalComponent
@@ -292,27 +292,27 @@ under the License.
         </license>
       </additionalComponent>
       <additionalComponent id="jackson-dataformat-xml">
-        <sbomLink>
+        <cdx:sbomLink>
           <url 
url="https://repo1.maven.org/maven2/com/fasterxml/jackson/dataformat/jackson-dataformat-xml/2.22.1/jackson-dataformat-xml-2.22.1-sbom-cyclonedx.json"/>
-        </sbomLink>
+        </cdx:sbomLink>
         <file file="${lib.dir.compile}/jackson-dataformat-xml-2.22.1.jar"/>
       </additionalComponent>
       <additionalComponent>
-        <sbomLink>
+        <cdx:sbomLink>
           <url 
url="https://repo1.maven.org/maven2/com/fasterxml/jackson/core/jackson-core/2.22.1/jackson-core-2.22.1-sbom-cyclonedx.json"/>
-        </sbomLink>
+        </cdx:sbomLink>
         <file file="${lib.dir.compile}/jackson-core-2.22.1.jar"/>
       </additionalComponent>
       <additionalComponent>
-        <sbomLink>
+        <cdx:sbomLink>
           <url 
url="https://repo1.maven.org/maven2/com/fasterxml/jackson/core/jackson-annotations/2.22/jackson-annotations-2.22-sbom-cyclonedx.json"/>
-        </sbomLink>
+        </cdx:sbomLink>
         <file file="${lib.dir.compile}/jackson-annotations-2.22.jar"/>
       </additionalComponent>
       <additionalComponent>
-        <sbomLink>
+        <cdx:sbomLink>
           <url 
url="https://repo1.maven.org/maven2/com/fasterxml/jackson/core/jackson-databind/2.22.1/jackson-databind-2.22.1-sbom-cyclonedx.json"/>
-        </sbomLink>
+        </cdx:sbomLink>
         <file file="${lib.dir.compile}/jackson-databind-2.22.1.jar"/>
       </additionalComponent>
       <additionalComponent
@@ -345,9 +345,9 @@ under the License.
         <license refid="apache-2"/>
       </additionalComponent>
       <additionalComponent id="apache-ivy">
-        <sbomLink>
+        <cdx:sbomLink>
           <url 
url="https://repo1.maven.org/maven2/org/apache/ivy/ivy/2.6.0/ivy-2.6.0.cdx.json"/>
-        </sbomLink>
+        </cdx:sbomLink>
         <file file="ivy/ivy.jar"/>
       </additionalComponent>
       <license refid="apache-2"/>
@@ -444,7 +444,9 @@ under the License.
         binarysource="Source Distribution"/>
   </target>
 
-  <target name="after-bin-dist" depends="after-dist" if="can.use.cyclonedx">
+  <target name="after-bin-dist" depends="after-dist"
+          if="can.use.cyclonedx"
+          xmlns:cdx="antlib:org.apache.ant.cyclonedx">
     <create-tarball-boms
         distdir="${bin.dist.dir}"
         distbase="${dist.base}"
@@ -452,15 +454,17 @@ under the License.
         binsrc="bin"
         binarysource="Binary Distribution">
       <component>
-        <sbomLink createBomExternalReference="false">
+        <cdx:sbomLink createBomExternalReference="false">
           <file file="${bin.dist.dir}/${artifact.stub}.cdx.json"/>
-        </sbomLink>
+        </cdx:sbomLink>
         <file file="${bin.dist.dir}/${artifact.stub}.jar"/>
       </component>
     </create-tarball-boms>
   </target>
 
-  <target name="after-fat-bin-dist" depends="after-dist" 
if="can.use.cyclonedx">
+  <target name="after-fat-bin-dist" depends="after-dist"
+          if="can.use.cyclonedx"
+          xmlns:cdx="antlib:org.apache.ant.cyclonedx">
     <create-tarball-boms
         distdir="${bin.dist.dir}"
         distbase="${dist.base}"
@@ -468,9 +472,9 @@ under the License.
         binsrc="bin-withdeps"
         binarysource="Binary Distribution Including Dependencies">
       <component>
-        <sbomLink createBomExternalReference="false">
+        <cdx:sbomLink createBomExternalReference="false">
           <file file="${bin.dist.dir}/${artifact.stub}.cdx.json"/>
-        </sbomLink>
+        </cdx:sbomLink>
         <file file="${bin.dist.dir}/${artifact.stub}.jar"/>
       </component>
     </create-tarball-boms>
diff --git a/changes.xml b/changes.xml
index d35269d..37c4e75 100644
--- a/changes.xml
+++ b/changes.xml
@@ -98,10 +98,15 @@
       The default SBOM version now is 1.7 and the isExternal attribute
       of component now also sets the attribute in the generated SBOM.
     </action>
+    <action type="add" breaks-bwc="true">
+      Generalized sbomLink to a ComponentResolver componentdef. As a
+      side effect the nested sbomLink element of component now must
+      live in the "antlib:org.apache.ant.cyclonedx" XML namespace
+      (i.e. something like cdx:sbomLink).
+    </action>
     <action type="add">
-      The component type now supports a new nested element "ivymodule"
-      which uses Apache Ivy to fill in the details of a component and
-      its dependencies.
+      Added a ComponentResolver "ivyModule" which uses Apache Ivy to
+      fill in the details of a component and its dependencies.
       This adds Ivy as a new dependency which is only required if you
       want to use this feature.
     </action>
diff --git a/docs/component.html b/docs/component.html
index a4b2c8a..1716483 100644
--- a/docs/component.html
+++ b/docs/component.html
@@ -151,302 +151,17 @@ <h4>any
       describes. This is required if you want to include hashes for
       the component in your SBOM.</p>
 
-    <h4 id="sbomLink">sbomLink</h4>
-
-    <p>At most one resource can be specified
-      as <code>sbomLink</code>. When present the referenced resource is
-      read as CycloneDX SBOM and:</p>
-
-    <ul>
-      <li><code>type</code>, <code>name</code>, <code>group</code>, 
<code>version</code>,
-        <code>purl</code>, <code>bomRef</code>, <code>scope</code>,
-        <code>decription</code>, <code>publisher</code>, <code>copright</code>,
-        <code>mimeType</code> and <code>supplier</code> are taken
-        from the SBOM's metadata component unless they are explicitly
-        specified on the component element itself.</li>
-      <li><code>manufacturer</code> is taken from the SBOM's metadata
-        component unless it is explicitly specified on the component
-        element itself or <code>supplierismanufacturer</code>
-        is <code>true</code>.</li>
-      <li>Tags are merged with those of the SBOM's metadata
-        component.</li>
-      <li><code>author</code>s, <code>license</code>s, 
<code>externalReference</code>s,
-        <code>dependency</code>s and nested <code>components</code>
-        are taken from the SBOM's metadata component if and only if
-        there is no corresponding element in this component
-        element.</li>
-      <li>a "bom"-type <code>externalReference</code> is added to the
-        enclosing component if the nested resource provides an URL,
-        the enclosing component doesn't already
-        contain <code>externalReference</code>s, the parsed SBOM
-        doesn't already contain such an <code>externalReference</code>
-        and this element's <code>createBomExternalReference</code> is
-        not <code>false</code>.</li>
-      <li>Hashes of the linked SBOM are ignored completely.</li>
-      <li>Other components specified in the linked SBOM are also added
-        to the SBOM created by the compomentbom task if they are
-        direct dependencies of the current component element. And
-        their dependencies are set to "unknown" as handling of
-        transitive dependencies is beyond the scope of this
-        library.<br/>
-        Components specified explicitly in the SBOM take precedence
-        over those found by parsing an SBOM link. If there already
-        exists a component as part of the SBOM with the same name and
-        group as one read from the linked SBOM, the linked component
-        will be ignored. Here the version is ignored, it is assumed
-        the component explicitly specified is the result of a process
-        that resolved conflicts in dependency versions. Currently this
-        only applies to Components with <code>pkg:maven/</code> Package-URLs
-        as bom-ref.
-      </li>
-    </ul>
-
-    <p>The <a hre="https://github.com/CycloneDX/cyclonedx-core-java";>CycloneDX
-        Core (Java) library</a> is used to read the linked SBOM.</p>
-
-    <p>The <code>sbomLink</code> and <code>ivyModule</code> elements
-      can not both be used as nested elements of the same
-      component.</p>
+    <h4 id="componentResolver">any nested ComponentResolver</h4>
 
-    <h5>Attributes</h5>
-
-    <table class="attr">
-      <tr>
-        <th scope="col">Attribute</th>
-        <th scope="col">Description</th>
-        <th scope="col">Required</th>
-      </tr>
-      <tr>
-        <td>createBomExternalReference</td>
-        <td>Whether to create a bom-Type external reference in the
-          resolved compoment based on the nested resource's URI.<br>
-          <em>since CycloneDX Antlib 0.2</em></td>
-        <td>No, defaults to <code>true</code></td>
-      </tr>
-    </table>
-
-    <h4 id="ivyModule">ivyModule</h4>
-
-    <p><em>since CycloneDX Antlib 0.2</em>. The optional Ivy
-      dependency is required when using an <code>ivyModule</code>
-      element.</p>
-
-    <p>Using a nesed <code>ivyModule</code> you can use Apache Ivy to
-      fill in fields from an <code>ivy.xml</code> file and have Ivy
-      provide all (transitive) dependencies of this module to the
-      SBOM.</p>
-
-    <p>The <code>ivyModule</code> element is similar to
-      a <a 
href="https://ant.apache.org/ivy/history/2.6.0/use/postresolvetask.html";>post
-      resolve task</a> and expects the Ivy module used for the current
-      component to be "resolved" already.</p>
-
-    <p>When present the Ivy module wll be used and</p>
-
-    <ul>
-      <li><code>name</code>, <code>group</code>, <code>version</code>,
-        <code>decription</code> are directly taken from the Ivy module
-        unless the compoment already specifies it explicitly.</li>
-      <li><code>license</code> and
-        "website"-type <code>externalReference</code> are taken from
-        the module's license and homePage if and only if there is no
-        corresponding element in this component element.</li>
-      <li>Dependencies of the given module that are included because
-        of the configurations given by the <code>conf</code> attribute
-        are added to the SBOM by the compomentbom task with data taken
-        from their module descriptors. This includes transitive
-        dependencies. The components will have hashes if an Ivy
-        retrieve operation has retrieved them and local files are
-        available prior to running the componentbom task.<br/>
-        Components specified explicitly in the SBOM take precedence
-        over those found by parsing the module. If there already
-        exists a component as part of the SBOM with the same name and
-        group as one read from the module, the dependencies will be
-        ignored. Here the version is ignored, it is assumed the
-        component explicitly specified is the result of a process that
-        resolved conflicts in dependency versions and is unknown to
-        Ivy. Currently this only applies to Components
-        with <code>pkg:maven/</code> Package-URLs as bom-ref.
-      </li>
-    </ul>
-
-    <p>Not all parts of an SBOM component you may want to provide are
-      part of an Ivy module definition and you may want to provide
-      them explicitly. Supplier and manufacturer information, more
-      external references or the publisher are missing for
-      example. The <code>type</code> defaults to "library" and
-      the <code>scope</code> to "required". For the component that
-      contains the <code>ivyModule</code> child element itself, you
-      can use the "normal" attributes and nested element of the
-      component to augment data - for the dependencies identified by
-      the Ivy module you can provide extra data
-      as <code>templateComponent</code> child elements.</p>
-
-    <p>The <code>sbomLink</code> and <code>ivyModule</code> elements
-      can not both be used as nested elements of the same
-      component.</p>
-
-    <h5>Attributes</h5>
-
-    <table class="attr">
-      <tr>
-        <th scope="col">Attribute</th>
-        <th scope="col">Description</th>
-        <th scope="col">Required</th>
-      </tr>
-      <tr>
-        <td>conf</td>
-        <td>comma-separated list of configurations that will be
-          consulted for dependencies of this component.</td>
-        <td>No, defaults to <code>*</code></td>
-      </tr>
-      <tr>
-        <td>optionalconf</td>
-        <td>comma-separated list of configurations. Any dependency
-          that is only included because of configurations specified in
-          this list will be marked with <code>scope</code>
-          optional.</td>
-        <td>No, by default no dependency is marked as optional.</td>
-      </tr>
-      <tr>
-        <td>externalconf</td>
-        <td>comma-separated list of configurations. Any dependency
-          that is only included because of configurations specified in
-          this list will have their <code>isExternal</code> flag set
-          to true.</td>
-        <td>No, by default no dependency is marked as external.</td>
-      </tr>
-      <tr>
-        <td>resolveId</td>
-        <td>The id which was used for a previous resolve.</td>
-        <td>No, defaults to <code>[org]-[module]</code> of the last
-          <code>ivy:resolve</code> task.</td>
-      </tr>
-      <tr>
-        <td>settingsRef</td>
-        <td>A reference to Ivy settings that must be used.</td>
-        <td>No, defaults to <code>ivy.instance</code>.</td>
-      </tr>
-      <tr>
-        <td>pattern</td>
-        <td>The pattern used by the <code>ivy:retrieve</code> task
-          that copied the dependencies.</td>
-        <td>No, defaults to <code>ivy.retrieve.pattern</code>.</td>
-      </tr>
-    </table>
-
-    <h5>Nested elements</h5>
-
-    <h6>templateComponent</h6>
-
-    <p>A nested <code>templateComponent</code> element supports all
-      attributes and nested elements of a component, but
-      neither <code>sbomLink</code> nor <code>ivyModule</code> have
-      any effect.</p>
-
-    <p>A <code>templateComponent</code> can be used to enrich a
-      component detected by Ivy as a dependency of the current
-      component. For example by adding more external references or a
-      manufacturer. Template components take precedence over a data
-      detected by Ivy, they are matched to a detected dependency by
-      name and group only, the version is ignored. If
-      the <code>templateComponent</code> has a <code>version</code>
-      attribute, no hashes will be calculated from the resolved
-      file.</p>
-
-    <p>The combination
-      of <code>ivyModule</code>, <code>templateComponent</code> and
-      an <code>additionalComponent</code> specified in
-      a <code>compomentbom</code> may be a bit
-      confusing. An <code>additionalComponent</code> is always added
-      to the generated SBOM and any <code>ivyModule</code> dependency
-      or <code>templateComponent</code> that matches the name and
-      group of an <code>additionalComponent</code> is ignored. Apart
-      from that a <code>templateComponent</code> will only show up in
-      the SBOM if a matching dependency is detected
-      by <code>ivyModule</code>. <code>templateComponent</code> and
-      Ivy module dependencies are merged
-      while <code>additionalComponent</code>s completely replace Ivy
-      module dependencies.</p>
-
-    <p>As an example, assume the <code>ivyModule</code> resolves a
-      module with group "org.example", name "module" and version "1"
-      which has a dependency on a component with group "com.example",
-      name "dependency", version "2" and description "my
-      description".</p>
-
-    <p>The following snippet adds two components to the SBOM with
-      bom-refs "pkg:maven/org.example/module@1?type=jar" and
-      "pkg:maven/com.example/dependency@2?type=jar" where the later has a
-      description of "my description" and hashes if the dependency has
-      been retrieved:</p>
-
-    <pre>
-      &lt;cdx:componentbom
-        &lt;cdx:component>
-          &lt;ivyModule/>
-        &lt;/cdx:component>
-      &lt;/cdx:componentbom>
-    </pre>
-
-    <p>The following snippet adds two components to the SBOM with
-      bom-refs "pkg:maven/org.example/module@1?type=jar" and
-      "pkg:maven/com.example/dependency@3?type=jar" where the later has
-      a no description at all and no hashes either.</p>
-
-    <pre>
-      &lt;cdx:componentbom
-        &lt;cdx:component>
-          &lt;ivyModule/>
-        &lt;/cdx:component>
-        &lt;cdx:additionalComponent
-            name="dependency"
-            group="com.example"
-            version="3"
-          />
-      &lt;/cdx:componentbom>
-    </pre>
+    <p>This library defines a <code>ComponentResolver</code> interface
+      that can be implemented by Ant types to enrich its parent
+      component by reading external data.</p>
 
-    <p>The following snippet adds two components to the SBOM with
-      bom-refs "pkg:maven/org.example/module@1?type=jar" and
-      "pkg:maven/com.example/dependency@2?type=jar" where the later
-      has a description "my description" a publisher "example" and
-      hashes if the dependency has been retrieved.</p>
+    <p>Currently the library ships with two
+      implementations: <a href="sbomLink.html">sbomLink</a>
+      and <a href="ivyModule.html">ivyModule</a>.</p>
 
-    <pre>
-      &lt;cdx:componentbom
-        &lt;cdx:component>
-          &lt;ivyModule>
-            &lt;cdx:templateComponent
-                name="dependency"
-                group="com.example"
-                publisher="Example"
-              />
-          &lt;/ivyModule>
-        &lt;/cdx:component>
-      &lt;/cdx:componentbom>
-    </pre>
-
-    <p>The following snippet adds two components to the SBOM with
-      bom-refs "pkg:maven/org.example/module@1?type=jar" and
-      "pkg:maven/com.example/dependency@3?type=jar" where the later has
-      a description "another description" but no hashes (as the
-      version doesn't match).</p>
-
-    <pre>
-      &lt;cdx:componentbom
-        &lt;cdx:component>
-          &lt;ivyModule>
-            &lt;cdx:templateComponent
-                name="dependency"
-                group="com.example"
-                version="3"
-                description="another description"
-              />
-          &lt;/ivyModule>
-        &lt;/cdx:component>
-      &lt;/cdx:componentbom>
-    </pre>
+    <p>You must not specify more than one nested ComponentResolver.</p>
 
     <h4 id="manufacturer">manufacturer</h4>
 
diff --git a/docs/componentbom.html b/docs/componentbom.html
index 4905e50..d80c8d5 100644
--- a/docs/componentbom.html
+++ b/docs/componentbom.html
@@ -33,10 +33,10 @@ <h2 id="componentbom">componentbom Task</h2>
       required that specifies the component to create an SBOM
       for.</p>
 
-    <p>Components that get "resolved" because they specify
-      an <code>sbomLink</code> can add more components to the SBOM than
-      are specified explicitly via the child elements of this
-      task.</p>
+    <p>Components that get "resolved" because they specify a nested
+      resolver like <code>sbomLink</code> or <code>ivyModule</code>
+      can add more components to the SBOM than are specified
+      explicitly via the child elements of this task.</p>
 
     <p>It is an error if any component specifies a dependency on a
       component that is not part of the generated SBOM. The bom-refs
diff --git a/docs/ivyModule.html b/docs/ivyModule.html
new file mode 100644
index 0000000..dfcd652
--- /dev/null
+++ b/docs/ivyModule.html
@@ -0,0 +1,246 @@
+<!--
+   Licensed to the Apache Software Foundation (ASF) under one or more
+   contributor license agreements.  See the NOTICE file distributed with
+   this work for additional information regarding copyright ownership.
+   The ASF licenses this file to You under the Apache License, Version 2.0
+   (the "License"); you may not use this file except in compliance with
+   the License.  You may obtain a copy of the License at
+
+       https://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.
+-->
+<html>
+  <head>
+    <meta http-equiv="Content-Language" content="en-us"></meta>
+    <link rel="stylesheet" type="text/css" href="style.css">
+    <title>Apache CycloneDX Ant Library - ivyModule ComponentResolver</title>
+  </head>
+
+  <body>
+    <h2 id="ivyModule">ivyModule ComponentResolver</h2>
+
+    <p><em>since CycloneDX Antlib 0.2</em>. The optional Ivy
+      dependency is required when using an <code>ivyModule</code>
+      element.</p>
+
+    <p>Using a <code>ivyModule</code> ComponentResolver within a
+      component you can use Apache Ivy to fill in fields from
+      an <code>ivy.xml</code> file and have Ivy provide all
+      (transitive) dependencies of this module to the SBOM.</p>
+
+    <p>The <code>ivyModule</code> element is similar to
+      a <a 
href="https://ant.apache.org/ivy/history/2.6.0/use/postresolvetask.html";>post
+      resolve task</a> and expects the Ivy module used for the current
+      component to be "resolved" already.</p>
+
+    <p>When present the Ivy module will be used to enrich the
+      component it is nested into:</p>
+
+    <ul>
+      <li><code>name</code>, <code>group</code>, <code>version</code>,
+        <code>decription</code> are directly taken from the Ivy module
+        unless the compoment already specifies it explicitly.</li>
+      <li><code>license</code> and
+        "website"-type <code>externalReference</code> are taken from
+        the module's license and homePage if and only if there is no
+        corresponding element in this component element.</li>
+      <li>Dependencies of the given module that are included because
+        of the configurations given by the <code>conf</code> attribute
+        are added to the SBOM by the compomentbom task with data taken
+        from their module descriptors. This includes transitive
+        dependencies. The components will have hashes if an Ivy
+        retrieve operation has retrieved them and local files are
+        available prior to running the componentbom task.<br/>
+        Components specified explicitly in the SBOM take precedence
+        over those found by parsing the module. If there already
+        exists a component as part of the SBOM with the same name and
+        group as one read from the module, the dependencies will be
+        ignored. Here the version is ignored, it is assumed the
+        component explicitly specified is the result of a process that
+        resolved conflicts in dependency versions and is unknown to
+        Ivy. Currently this only applies to Components
+        with <code>pkg:maven/</code> Package-URLs as bom-ref.
+      </li>
+    </ul>
+
+    <p>Not all parts of an SBOM component you may want to provide are
+      part of an Ivy module definition and you may want to provide
+      them explicitly. Supplier and manufacturer information, more
+      external references or the publisher are missing for
+      example. The <code>type</code> defaults to "library" and
+      the <code>scope</code> to "required". For the component that
+      contains the <code>ivyModule</code> child element itself, you
+      can use the "normal" attributes and nested element of the
+      component to augment data - for the dependencies identified by
+      the Ivy module you can provide extra data
+      as <code>templateComponent</code> child elements.</p>
+
+    <h3>Attributes</h3>
+
+    <table class="attr">
+      <tr>
+        <th scope="col">Attribute</th>
+        <th scope="col">Description</th>
+        <th scope="col">Required</th>
+      </tr>
+      <tr>
+        <td>conf</td>
+        <td>comma-separated list of configurations that will be
+          consulted for dependencies of this component.</td>
+        <td>No, defaults to <code>*</code></td>
+      </tr>
+      <tr>
+        <td>optionalconf</td>
+        <td>comma-separated list of configurations. Any dependency
+          that is only included because of configurations specified in
+          this list will be marked with <code>scope</code>
+          optional.</td>
+        <td>No, by default no dependency is marked as optional.</td>
+      </tr>
+      <tr>
+        <td>externalconf</td>
+        <td>comma-separated list of configurations. Any dependency
+          that is only included because of configurations specified in
+          this list will have their <code>isExternal</code> flag set
+          to true.</td>
+        <td>No, by default no dependency is marked as external.</td>
+      </tr>
+      <tr>
+        <td>resolveId</td>
+        <td>The id which was used for a previous resolve.</td>
+        <td>No, defaults to <code>[org]-[module]</code> of the last
+          <code>ivy:resolve</code> task.</td>
+      </tr>
+      <tr>
+        <td>settingsRef</td>
+        <td>A reference to Ivy settings that must be used.</td>
+        <td>No, defaults to <code>ivy.instance</code>.</td>
+      </tr>
+      <tr>
+        <td>pattern</td>
+        <td>The pattern used by the <code>ivy:retrieve</code> task
+          that copied the dependencies.</td>
+        <td>No, defaults to <code>ivy.retrieve.pattern</code>.</td>
+      </tr>
+    </table>
+
+    <h3>Nested elements</h3>
+
+    <h4>templateComponent</h4>
+
+    <p>A nested <code>templateComponent</code> element supports all
+      attributes and nested elements of a component, but
+      neither <code>sbomLink</code> nor <code>ivyModule</code> have
+      any effect.</p>
+
+    <p>A <code>templateComponent</code> can be used to enrich a
+      component detected by Ivy as a dependency of the current
+      component. For example by adding more external references or a
+      manufacturer. Template components take precedence over a data
+      detected by Ivy, they are matched to a detected dependency by
+      name and group only, the version is ignored. If
+      the <code>templateComponent</code> has a <code>version</code>
+      attribute, no hashes will be calculated from the resolved
+      file.</p>
+
+    <p>The combination
+      of <code>ivyModule</code>, <code>templateComponent</code> and
+      an <code>additionalComponent</code> specified in
+      a <code>compomentbom</code> may be a bit
+      confusing. An <code>additionalComponent</code> is always added
+      to the generated SBOM and any <code>ivyModule</code> dependency
+      or <code>templateComponent</code> that matches the name and
+      group of an <code>additionalComponent</code> is ignored. Apart
+      from that a <code>templateComponent</code> will only show up in
+      the SBOM if a matching dependency is detected
+      by <code>ivyModule</code>. <code>templateComponent</code> and
+      Ivy module dependencies are merged
+      while <code>additionalComponent</code>s completely replace Ivy
+      module dependencies.</p>
+
+    <p>As an example, assume the <code>ivyModule</code> resolves a
+      module with group "org.example", name "module" and version "1"
+      which has a dependency on a component with group "com.example",
+      name "dependency", version "2" and description "my
+      description".</p>
+
+    <p>The following snippet adds two components to the SBOM with
+      bom-refs "pkg:maven/org.example/module@1?type=jar" and
+      "pkg:maven/com.example/dependency@2?type=jar" where the later has a
+      description of "my description" and hashes if the dependency has
+      been retrieved:</p>
+
+    <pre>
+      &lt;cdx:componentbom
+        &lt;cdx:component>
+          &lt;cdx:ivyModule/>
+        &lt;/cdx:component>
+      &lt;/cdx:componentbom>
+    </pre>
+
+    <p>The following snippet adds two components to the SBOM with
+      bom-refs "pkg:maven/org.example/module@1?type=jar" and
+      "pkg:maven/com.example/dependency@3?type=jar" where the later has
+      a no description at all and no hashes either.</p>
+
+    <pre>
+      &lt;cdx:componentbom
+        &lt;cdx:component>
+          &lt;cdx:ivyModule/>
+        &lt;/cdx:component>
+        &lt;cdx:additionalComponent
+            name="dependency"
+            group="com.example"
+            version="3"
+          />
+      &lt;/cdx:componentbom>
+    </pre>
+
+    <p>The following snippet adds two components to the SBOM with
+      bom-refs "pkg:maven/org.example/module@1?type=jar" and
+      "pkg:maven/com.example/dependency@2?type=jar" where the later
+      has a description "my description" a publisher "example" and
+      hashes if the dependency has been retrieved.</p>
+
+    <pre>
+      &lt;cdx:componentbom
+        &lt;cdx:component>
+          &lt;cdx:ivyModule>
+            &lt;templateComponent
+                name="dependency"
+                group="com.example"
+                publisher="Example"
+              />
+          &lt;/cdx:ivyModule>
+        &lt;/cdx:component>
+      &lt;/cdx:componentbom>
+    </pre>
+
+    <p>The following snippet adds two components to the SBOM with
+      bom-refs "pkg:maven/org.example/module@1?type=jar" and
+      "pkg:maven/com.example/dependency@3?type=jar" where the later has
+      a description "another description" but no hashes (as the
+      version doesn't match).</p>
+
+    <pre>
+      &lt;cdx:componentbom
+        &lt;cdx:component>
+          &lt;cdx:ivyModule>
+            &lt;templateComponent
+                name="dependency"
+                group="com.example"
+                version="3"
+                description="another description"
+              />
+          &lt;/cdx:ivyModule>
+        &lt;/cdx:component>
+      &lt;/cdx:componentbom>
+    </pre>
+  </body>
+</html>
+    
diff --git a/docs/sbomLink.html b/docs/sbomLink.html
new file mode 100644
index 0000000..988d5b3
--- /dev/null
+++ b/docs/sbomLink.html
@@ -0,0 +1,98 @@
+<!--
+   Licensed to the Apache Software Foundation (ASF) under one or more
+   contributor license agreements.  See the NOTICE file distributed with
+   this work for additional information regarding copyright ownership.
+   The ASF licenses this file to You under the Apache License, Version 2.0
+   (the "License"); you may not use this file except in compliance with
+   the License.  You may obtain a copy of the License at
+
+       https://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.
+-->
+<html>
+  <head>
+    <meta http-equiv="Content-Language" content="en-us"></meta>
+    <link rel="stylesheet" type="text/css" href="style.css">
+    <title>Apache CycloneDX Ant Library - sbomLink ComponentResolver</title>
+  </head>
+
+  <body>
+    <h2 id="sbomLink">sbomLink ComponentResolver</h2>
+
+    <p>Enriches a component by reading an existing CycloneDX SBOM.</p>
+
+    <p>At most one resource can be specified as nested element which
+      is then read as CycloneDX SBOM and enriches the component that
+      contains this ComponentResolver:</p>
+
+    <ul>
+      <li><code>type</code>, <code>name</code>, <code>group</code>, 
<code>version</code>,
+        <code>purl</code>, <code>bomRef</code>, <code>scope</code>,
+        <code>decription</code>, <code>publisher</code>, <code>copright</code>,
+        <code>mimeType</code> and <code>supplier</code> are taken
+        from the SBOM's metadata component unless they are explicitly
+        specified on the component element itself.</li>
+      <li><code>manufacturer</code> is taken from the SBOM's metadata
+        component unless it is explicitly specified on the component
+        element itself or <code>supplierismanufacturer</code>
+        is <code>true</code>.</li>
+      <li>Tags are merged with those of the SBOM's metadata
+        component.</li>
+      <li><code>author</code>s, <code>license</code>s, 
<code>externalReference</code>s,
+        <code>dependency</code>s and nested <code>components</code>
+        are taken from the SBOM's metadata component if and only if
+        there is no corresponding element in this component
+        element.</li>
+      <li>a "bom"-type <code>externalReference</code> is added to the
+        enclosing component if the nested resource provides an URL,
+        the enclosing component doesn't already
+        contain <code>externalReference</code>s, the parsed SBOM
+        doesn't already contain such an <code>externalReference</code>
+        and this element's <code>createBomExternalReference</code> is
+        not <code>false</code>.</li>
+      <li>Hashes of the linked SBOM are ignored completely.</li>
+      <li>Other components specified in the linked SBOM are also added
+        to the SBOM created by the compomentbom task if they are
+        direct dependencies of the current component element. And
+        their dependencies are set to "unknown" as handling of
+        transitive dependencies is beyond the scope of this
+        library.<br/>
+        Components specified explicitly in the SBOM take precedence
+        over those found by parsing an SBOM link. If there already
+        exists a component as part of the SBOM with the same name and
+        group as one read from the linked SBOM, the linked component
+        will be ignored. Here the version is ignored, it is assumed
+        the component explicitly specified is the result of a process
+        that resolved conflicts in dependency versions. Currently this
+        only applies to Components with <code>pkg:maven/</code> Package-URLs
+        as bom-ref.
+      </li>
+    </ul>
+
+    <p>The <a hre="https://github.com/CycloneDX/cyclonedx-core-java";>CycloneDX
+        Core (Java) library</a> is used to read the linked SBOM.</p>
+
+    <h3>Attributes</h3>
+
+    <table class="attr">
+      <tr>
+        <th scope="col">Attribute</th>
+        <th scope="col">Description</th>
+        <th scope="col">Required</th>
+      </tr>
+      <tr>
+        <td>createBomExternalReference</td>
+        <td>Whether to create a bom-Type external reference in the
+          resolved compoment based on the nested resource's URI.<br>
+          <em>since CycloneDX Antlib 0.2</em></td>
+        <td>No, defaults to <code>true</code></td>
+      </tr>
+    </table>
+
+  </body>
+</html>    
diff --git a/example/ant-cyclonedx-0.2alpha.cdx.diff 
b/example/ant-cyclonedx-0.2alpha.cdx.diff
index 735bb7e..e84f2c0 100644
--- a/example/ant-cyclonedx-0.2alpha.cdx.diff
+++ b/example/ant-cyclonedx-0.2alpha.cdx.diff
@@ -1,5 +1,5 @@
---- ant-cyclonedx-0.2alpha.cdx.json    2026-08-16 12:54:15.035134147 +0200
-+++ ant-cyclonedx-0.2alpha.ivy.cdx.json        2026-08-16 12:54:22.467055136 
+0200
+--- ant-cyclonedx-0.2alpha.cdx.json    2026-08-16 16:28:01.254033959 +0200
++++ ant-cyclonedx-0.2alpha.ivy.cdx.json        2026-08-16 16:28:03.535753306 
+0200
 @@ -253,36 +253,68 @@
    "components" : [
      {
diff --git a/example/ant-cyclonedx-0.2alpha.cdx.json 
b/example/ant-cyclonedx-0.2alpha.cdx.json
index 1c7407b..09603df 100644
--- a/example/ant-cyclonedx-0.2alpha.cdx.json
+++ b/example/ant-cyclonedx-0.2alpha.cdx.json
@@ -34,35 +34,35 @@
           "hashes" : [
             {
               "alg" : "MD5",
-              "content" : "de73b2090ed784264d7cdf6b94c40b9f"
+              "content" : "f7e5e0f28ff8599952690ffbcee84cb8"
             },
             {
               "alg" : "SHA-1",
-              "content" : "117736e22f1f27217eaf9068b1007fddedef5f8f"
+              "content" : "519987c92a03c45a826479d21a32280e4d319d55"
             },
             {
               "alg" : "SHA-256",
-              "content" : 
"aed1a008c3ebdbb2908d7dbacebbc1d8d489d08ebba0910217f239412ab36b04"
+              "content" : 
"3dc5e114e93941bc125fd46fa63fbb2bdccece34d9c9be8f314940766736bdd4"
             },
             {
               "alg" : "SHA-512",
-              "content" : 
"add20643bd0e106b27c7200863bf376ea28ee95cd42697820024b2eff5c5bdacc4be0bbbe1138ca8ac35e209df80ce30bd27e1a5eb32d1ae528234936f4ea0ca"
+              "content" : 
"8c27052202f363a72e49306a4387e3b7869e51093c8c72f4ec3aa1d04067cc62cb44e2fb765b7035bd553b2320a15568eb2c896f08ce1d489f2c4fe4545211fa"
             },
             {
               "alg" : "SHA3-256",
-              "content" : 
"192621053a51c43cc73078977487e42ec5b6597c5e95119ba86a283b1aca4470"
+              "content" : 
"c90ceaa8a35b26c169c97efa7bbeff6eedc790edf92b21699d5330104159cb88"
             },
             {
               "alg" : "SHA3-512",
-              "content" : 
"11159291538dd1d7fec1de2214579732e76a1233acbff6887ead1b7520bdf784f47f0f6af1ca66f0096ddbbbf7470febd77369260653781ee4ed1b3a6d465c20"
+              "content" : 
"b3acbc1c48365547ff8c3902cf58b82e78ffa8eac2764a66f360b7d6ffaf698d7a7b98fc300efc86e725ea6b850ed8dad04d5faa19acd101776b4a7762d95048"
             },
             {
               "alg" : "SHA-384",
-              "content" : 
"c65ed1eb81aa20245bab2173bcab863663dec015f220bc5c5353a0d099e71013f85537a41e8fb673dba35f8bf9ada61d"
+              "content" : 
"94a64422adff961a4afb215c3f097537edfec4590291920958ea455fc2240783f5b3462dd2fb7a4ed0224748724b6d22"
             },
             {
               "alg" : "SHA3-384",
-              "content" : 
"031ca0350bcb44f06f57160d0d9bba747f37a403a58b54938758daf84f2b34f07201a6a69052ee8bbbfb15f6df0dec3b"
+              "content" : 
"45066279dd52e01fff8c38f1d97c9116ff9be966559a006756000304502f6e5a8d370ed500fc370cb24ac7dda9289902"
             }
           ],
           "licenses" : [
@@ -146,35 +146,35 @@
       "hashes" : [
         {
           "alg" : "MD5",
-          "content" : "de73b2090ed784264d7cdf6b94c40b9f"
+          "content" : "f7e5e0f28ff8599952690ffbcee84cb8"
         },
         {
           "alg" : "SHA-1",
-          "content" : "117736e22f1f27217eaf9068b1007fddedef5f8f"
+          "content" : "519987c92a03c45a826479d21a32280e4d319d55"
         },
         {
           "alg" : "SHA-256",
-          "content" : 
"aed1a008c3ebdbb2908d7dbacebbc1d8d489d08ebba0910217f239412ab36b04"
+          "content" : 
"3dc5e114e93941bc125fd46fa63fbb2bdccece34d9c9be8f314940766736bdd4"
         },
         {
           "alg" : "SHA-512",
-          "content" : 
"add20643bd0e106b27c7200863bf376ea28ee95cd42697820024b2eff5c5bdacc4be0bbbe1138ca8ac35e209df80ce30bd27e1a5eb32d1ae528234936f4ea0ca"
+          "content" : 
"8c27052202f363a72e49306a4387e3b7869e51093c8c72f4ec3aa1d04067cc62cb44e2fb765b7035bd553b2320a15568eb2c896f08ce1d489f2c4fe4545211fa"
         },
         {
           "alg" : "SHA3-256",
-          "content" : 
"192621053a51c43cc73078977487e42ec5b6597c5e95119ba86a283b1aca4470"
+          "content" : 
"c90ceaa8a35b26c169c97efa7bbeff6eedc790edf92b21699d5330104159cb88"
         },
         {
           "alg" : "SHA3-512",
-          "content" : 
"11159291538dd1d7fec1de2214579732e76a1233acbff6887ead1b7520bdf784f47f0f6af1ca66f0096ddbbbf7470febd77369260653781ee4ed1b3a6d465c20"
+          "content" : 
"b3acbc1c48365547ff8c3902cf58b82e78ffa8eac2764a66f360b7d6ffaf698d7a7b98fc300efc86e725ea6b850ed8dad04d5faa19acd101776b4a7762d95048"
         },
         {
           "alg" : "SHA-384",
-          "content" : 
"c65ed1eb81aa20245bab2173bcab863663dec015f220bc5c5353a0d099e71013f85537a41e8fb673dba35f8bf9ada61d"
+          "content" : 
"94a64422adff961a4afb215c3f097537edfec4590291920958ea455fc2240783f5b3462dd2fb7a4ed0224748724b6d22"
         },
         {
           "alg" : "SHA3-384",
-          "content" : 
"031ca0350bcb44f06f57160d0d9bba747f37a403a58b54938758daf84f2b34f07201a6a69052ee8bbbfb15f6df0dec3b"
+          "content" : 
"45066279dd52e01fff8c38f1d97c9116ff9be966559a006756000304502f6e5a8d370ed500fc370cb24ac7dda9289902"
         }
       ],
       "licenses" : [
diff --git a/example/ant-cyclonedx-0.2alpha.ivy.cdx.json 
b/example/ant-cyclonedx-0.2alpha.ivy.cdx.json
index 667d8e0..cc60152 100644
--- a/example/ant-cyclonedx-0.2alpha.ivy.cdx.json
+++ b/example/ant-cyclonedx-0.2alpha.ivy.cdx.json
@@ -34,35 +34,35 @@
           "hashes" : [
             {
               "alg" : "MD5",
-              "content" : "de73b2090ed784264d7cdf6b94c40b9f"
+              "content" : "f7e5e0f28ff8599952690ffbcee84cb8"
             },
             {
               "alg" : "SHA-1",
-              "content" : "117736e22f1f27217eaf9068b1007fddedef5f8f"
+              "content" : "519987c92a03c45a826479d21a32280e4d319d55"
             },
             {
               "alg" : "SHA-256",
-              "content" : 
"aed1a008c3ebdbb2908d7dbacebbc1d8d489d08ebba0910217f239412ab36b04"
+              "content" : 
"3dc5e114e93941bc125fd46fa63fbb2bdccece34d9c9be8f314940766736bdd4"
             },
             {
               "alg" : "SHA-512",
-              "content" : 
"add20643bd0e106b27c7200863bf376ea28ee95cd42697820024b2eff5c5bdacc4be0bbbe1138ca8ac35e209df80ce30bd27e1a5eb32d1ae528234936f4ea0ca"
+              "content" : 
"8c27052202f363a72e49306a4387e3b7869e51093c8c72f4ec3aa1d04067cc62cb44e2fb765b7035bd553b2320a15568eb2c896f08ce1d489f2c4fe4545211fa"
             },
             {
               "alg" : "SHA3-256",
-              "content" : 
"192621053a51c43cc73078977487e42ec5b6597c5e95119ba86a283b1aca4470"
+              "content" : 
"c90ceaa8a35b26c169c97efa7bbeff6eedc790edf92b21699d5330104159cb88"
             },
             {
               "alg" : "SHA3-512",
-              "content" : 
"11159291538dd1d7fec1de2214579732e76a1233acbff6887ead1b7520bdf784f47f0f6af1ca66f0096ddbbbf7470febd77369260653781ee4ed1b3a6d465c20"
+              "content" : 
"b3acbc1c48365547ff8c3902cf58b82e78ffa8eac2764a66f360b7d6ffaf698d7a7b98fc300efc86e725ea6b850ed8dad04d5faa19acd101776b4a7762d95048"
             },
             {
               "alg" : "SHA-384",
-              "content" : 
"c65ed1eb81aa20245bab2173bcab863663dec015f220bc5c5353a0d099e71013f85537a41e8fb673dba35f8bf9ada61d"
+              "content" : 
"94a64422adff961a4afb215c3f097537edfec4590291920958ea455fc2240783f5b3462dd2fb7a4ed0224748724b6d22"
             },
             {
               "alg" : "SHA3-384",
-              "content" : 
"031ca0350bcb44f06f57160d0d9bba747f37a403a58b54938758daf84f2b34f07201a6a69052ee8bbbfb15f6df0dec3b"
+              "content" : 
"45066279dd52e01fff8c38f1d97c9116ff9be966559a006756000304502f6e5a8d370ed500fc370cb24ac7dda9289902"
             }
           ],
           "licenses" : [
@@ -146,35 +146,35 @@
       "hashes" : [
         {
           "alg" : "MD5",
-          "content" : "de73b2090ed784264d7cdf6b94c40b9f"
+          "content" : "f7e5e0f28ff8599952690ffbcee84cb8"
         },
         {
           "alg" : "SHA-1",
-          "content" : "117736e22f1f27217eaf9068b1007fddedef5f8f"
+          "content" : "519987c92a03c45a826479d21a32280e4d319d55"
         },
         {
           "alg" : "SHA-256",
-          "content" : 
"aed1a008c3ebdbb2908d7dbacebbc1d8d489d08ebba0910217f239412ab36b04"
+          "content" : 
"3dc5e114e93941bc125fd46fa63fbb2bdccece34d9c9be8f314940766736bdd4"
         },
         {
           "alg" : "SHA-512",
-          "content" : 
"add20643bd0e106b27c7200863bf376ea28ee95cd42697820024b2eff5c5bdacc4be0bbbe1138ca8ac35e209df80ce30bd27e1a5eb32d1ae528234936f4ea0ca"
+          "content" : 
"8c27052202f363a72e49306a4387e3b7869e51093c8c72f4ec3aa1d04067cc62cb44e2fb765b7035bd553b2320a15568eb2c896f08ce1d489f2c4fe4545211fa"
         },
         {
           "alg" : "SHA3-256",
-          "content" : 
"192621053a51c43cc73078977487e42ec5b6597c5e95119ba86a283b1aca4470"
+          "content" : 
"c90ceaa8a35b26c169c97efa7bbeff6eedc790edf92b21699d5330104159cb88"
         },
         {
           "alg" : "SHA3-512",
-          "content" : 
"11159291538dd1d7fec1de2214579732e76a1233acbff6887ead1b7520bdf784f47f0f6af1ca66f0096ddbbbf7470febd77369260653781ee4ed1b3a6d465c20"
+          "content" : 
"b3acbc1c48365547ff8c3902cf58b82e78ffa8eac2764a66f360b7d6ffaf698d7a7b98fc300efc86e725ea6b850ed8dad04d5faa19acd101776b4a7762d95048"
         },
         {
           "alg" : "SHA-384",
-          "content" : 
"c65ed1eb81aa20245bab2173bcab863663dec015f220bc5c5353a0d099e71013f85537a41e8fb673dba35f8bf9ada61d"
+          "content" : 
"94a64422adff961a4afb215c3f097537edfec4590291920958ea455fc2240783f5b3462dd2fb7a4ed0224748724b6d22"
         },
         {
           "alg" : "SHA3-384",
-          "content" : 
"031ca0350bcb44f06f57160d0d9bba747f37a403a58b54938758daf84f2b34f07201a6a69052ee8bbbfb15f6df0dec3b"
+          "content" : 
"45066279dd52e01fff8c38f1d97c9116ff9be966559a006756000304502f6e5a8d370ed500fc370cb24ac7dda9289902"
         }
       ],
       "licenses" : [
diff --git a/src/main/org/apache/ant/cyclonedx/Component.java 
b/src/main/org/apache/ant/cyclonedx/Component.java
index 9976de4..f2a8c42 100644
--- a/src/main/org/apache/ant/cyclonedx/Component.java
+++ b/src/main/org/apache/ant/cyclonedx/Component.java
@@ -38,7 +38,6 @@ import org.apache.tools.ant.types.DataType;
 import org.apache.tools.ant.types.Reference;
 import org.apache.tools.ant.types.Resource;
 import org.apache.tools.ant.types.resources.FileProvider;
-import org.apache.tools.ant.types.resources.Union;
 
 import org.cyclonedx.Version;
 import org.cyclonedx.model.LicenseChoice;
@@ -86,8 +85,7 @@ public class Component extends DataType {
     private Set<String> tags = new HashSet<>();
     private List<Property> properties = new ArrayList<>();
     private String mimeType;
-    private SbomLink sbomLink;
-    private IvyModule ivyModule;
+    private ComponentResolver resolver;
 
     /**
      * Comparator for components.
@@ -166,8 +164,7 @@ public class Component extends DataType {
         this.tags = new HashSet<>(other.tags);
         this.properties = new ArrayList<>(other.properties);
         this.mimeType = other.mimeType;
-        this.sbomLink = other.sbomLink;
-        this.ivyModule = other.ivyModule;
+        this.resolver = other.resolver;
     }
 
     /**
@@ -473,30 +470,16 @@ public class Component extends DataType {
     }
 
     /**
-     * Container for SBOM link resource.
-     *
-     * @return container for SBOM link resource
-     */
-    public SbomLink createSbomLink() {
-        checkChildrenAllowed();
-        if (ivyModule != null) {
-            throw new BuildException("sbomLink and ivyModule are mutually 
exclusive");
-        }
-        return sbomLink == null ? (sbomLink = new SbomLink(getProject())) : 
sbomLink;
-    }
-
-    /**
-     * Container for Ivy module configuration.
-     *
-     * @return container for Ivy module configuration
+     * Resolver that uses external data to enrich this component.
+     * @param resolver the resolver
      * @since CycloneDX Antlib 0.2
      */
-    public IvyModule createIvyModule() {
+    public void add(ComponentResolver resolver) {
         checkChildrenAllowed();
-        if (sbomLink != null) {
-            throw new BuildException("sbomLink and ivyModule are mutually 
exclusive");
+        if (this.resolver != null) {
+            throw new BuildException("only one resolver can be used at a 
time");
         }
-        return ivyModule == null ? (ivyModule = new IvyModule()) : ivyModule;
+        this.resolver = resolver;
     }
 
     /**
@@ -866,14 +849,7 @@ public class Component extends DataType {
         if (!resolved) {
             resolved = true;
 
-            if (sbomLink != null) {
-                SbomLinkComponentResolver resolver = new 
SbomLinkComponentResolver(getProject(), sbomLink);
-                return resolver.resolve(this);
-            }
-
-            if (ivyModule != null) {
-                IvyModuleComponentResolver resolver = new 
IvyModuleComponentResolver(ivyModule, getProject());
-
+            if (resolver != null) {
                 return resolver.resolve(this);
             }
         }
@@ -1319,179 +1295,4 @@ public class Component extends DataType {
             setChecked(true);
         }
     }
-
-    /**
-     * @since CycloneDX Antlib 0.2
-     */
-    public static class SbomLink extends Union {
-        private boolean createBomExternalReference = true;
-
-        public SbomLink(Project project) {
-            super(project);
-        }
-
-        /**
-         * Whether to create a bom-Type external reference in the
-         * resolved component based on the nested resource's URI.
-         *
-         * <p>Will not create an external reference of there are
-         * already external references om the component or the
-         * resolved SBOM already contains a bom-type reference.</p>
-         *
-         * <p>Defaults to <code>true</code>.
-         *
-         * @param create whether to create a bom-Type external reference
-         */
-        public void setCreateBomExternalReference(boolean create) {
-            createBomExternalReference = create;
-        }
-
-        /**
-         * Whether to create a bom-Type external reference in the
-         * resolved component based on the nested resource's URI.
-         * @return create whether to create a bom-Type external reference
-         */
-        public boolean getCreateBomExternalReference() {
-            return createBomExternalReference;
-        }
-    }
-
-    /**
-     * Configuration for Ivy module resolution.
-     *
-     * <p>This nested element allows a Component to be populated from
-     * an Ivy module descriptor. The Ivy file should already be resolved
-     * (i.e., ivy:resolve should have been run).</p>
-     *
-     * @since CycloneDX Antlib 0.2
-     */
-    public static class IvyModule {
-        private String conf;
-        private String optionalConf;
-        private String externalConf;
-        private String resolveId;
-        private Reference antIvyEngineRef;
-        private String pattern;
-        private Map<String, Component> templateComponents = new HashMap<>();
-
-        /**
-         * Sets the configurations to include in the SBOM.
-         *
-         * <p>Defaults to the configurations resolved by the last resolve 
call, or {@code *} if no resolve was
-         * explicitly called</p>
-         *
-         * @param comma separated list of the configurations to retrieve or 
{@code *}.
-         */
-        public void setConf(String conf) {
-            this.conf = conf;
-        }
-
-        String getConf() {
-            return conf;
-        }
-
-        /**
-         * Sets the id which was used for a previous resolve.
-         *
-         * <p>Defaults to {@code [org].[module]}.</p>
-         *
-         * @param id which was used for a previous resolve
-         */
-        public void setResolveId(String resolveId) {
-            this.resolveId = resolveId;
-        }
-
-        String getResolveId() {
-            return resolveId;
-        }
-
-        /**
-         * Sets a reference is a different Ivy settings file than the default 
shall be used.
-         *
-         * <p>Defaults to {@code ivy.instance}.</p>
-         *
-         * @param ref A reference to Ivy settings that must be used by this 
component
-         */
-        public void setSettingsRef(Reference ref) {
-            antIvyEngineRef = ref;
-        }
-
-        Reference getSettingsRef() {
-            return antIvyEngineRef;
-        }
-
-        /**
-         * Marks configurations as optional.
-         *
-         * <p>Any module that is included in the SBOM because it is required 
by on of the configurations given in {@link
-         * #setConf} and only is included because of configurations listed 
here is marked optional. Including
-         * configurations that are not part of {@link #setConf} doesn't have 
any effect. {@code *} is no supported. The
-         * default is to have no optional components.</p>
-         *
-         * @param comma separated list of the configurations to mark optional.
-         */
-        public void setOptionalConf(String optionalConf) {
-            this.optionalConf = optionalConf;
-        }
-
-        String getOptionalConf() {
-            return optionalConf;
-        }
-
-        /**
-         * Marks configurations as external.
-         *
-         * <p>Any module that is included in the SBOM because it is required 
by on of the configurations given in {@link
-         * #setConf} and only is included because of configurations listed 
here is marked external. Including
-         * configurations that are not part of {@link #setConf} doesn't have 
any effect. {@code *} is no supported. The
-         * default is to have no external components.</p>
-         *
-         * @param comma separated list of the configurations to mark external.
-         */
-        public void setExternalConf(String externalConf) {
-            this.externalConf = externalConf;
-        }
-
-        String getExternalConf() {
-            return externalConf;
-        }
-
-        /**
-         * The retrieve pattern used for retrieving the dependency artifacts.
-         *
-         * <p>Defaults to {@code ${ivy.retrieve.pattern}}.</p>
-         */
-        public void setPattern(String pattern) {
-            this.pattern = pattern;
-        }
-
-        String getPattern() {
-            return pattern;
-        }
-
-        /**
-         * Adds a nested template component.
-         *
-         * @param c nested template component
-         */
-        public void addConfiguredTemplateComponent(Component c) {
-            templateComponents.put(getTemplateComponentKey(c), c);
-        }
-
-        Map<String, Component> getTemplateComponents() {
-            return templateComponents;
-        }
-
-        private static String getTemplateComponentKey(Component c) {
-            String group = c.getGroup();
-            if (group == null) {
-                group = "";
-            }
-            String name = c.getName();
-            if (name == null) {
-                name = "";
-            }
-            return group + ":" + name;
-        }
-    }
 }
diff --git a/src/main/org/apache/ant/cyclonedx/ComponentResolver.java 
b/src/main/org/apache/ant/cyclonedx/ComponentResolver.java
new file mode 100644
index 0000000..3daace8
--- /dev/null
+++ b/src/main/org/apache/ant/cyclonedx/ComponentResolver.java
@@ -0,0 +1,41 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      https://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ */
+package org.apache.ant.cyclonedx;
+
+import java.io.IOException;
+import java.util.Collection;
+
+import org.apache.tools.ant.BuildException;
+
+/**
+ * Common interface for "resolvers" that read data of a component form 
external sources and enrich an existing component
+ * withe data read - and may even identify additional components.
+ *
+ * <p>Any additional component detected may be added to the SBOM. If another 
component already exists with the same
+ * group and name coordinates the additional component detected by this 
instance is ignored.</p>
+ */
+public interface ComponentResolver {
+    /**
+     * Resolves the external data and enriches the component with it.
+     *
+     * @param component the component to enrich
+     * @return additional components identified while reading external data
+     * @throws IOException if resolution fails
+     */
+    Collection<Component> resolve(Component component) throws IOException, 
BuildException;
+}
diff --git a/src/main/org/apache/ant/cyclonedx/IvyModuleComponentResolver.java 
b/src/main/org/apache/ant/cyclonedx/IvyModuleComponentResolver.java
index 58ba715..12a8c28 100644
--- a/src/main/org/apache/ant/cyclonedx/IvyModuleComponentResolver.java
+++ b/src/main/org/apache/ant/cyclonedx/IvyModuleComponentResolver.java
@@ -45,8 +45,10 @@ import org.apache.ivy.core.resolve.IvyNodeCallers.Caller;
 import org.apache.ivy.core.retrieve.RetrieveEngine;
 import org.apache.ivy.core.retrieve.RetrieveOptions;
 import org.apache.ivy.core.settings.IvySettings;
+
 import org.apache.tools.ant.BuildException;
 import org.apache.tools.ant.Project;
+import org.apache.tools.ant.ProjectComponent;
 import org.apache.tools.ant.types.Reference;
 import org.apache.tools.ant.types.resources.FileResource;
 import org.apache.tools.ant.types.resources.URLResource;
@@ -64,18 +66,99 @@ import org.cyclonedx.model.Component.Scope;
  *
  * @since CycloneDX Antlib 0.2
  */
-class IvyModuleComponentResolver {
+public class IvyModuleComponentResolver extends ProjectComponent implements 
ComponentResolver {
+
+    private String conf;
+    private String optionalConf;
+    private String externalConf;
+    private String resolveId;
+    private Reference settingRef;
+    private String pattern;
+    private Map<String, Component> templateComponents = new HashMap<>();
 
-    private final Component.IvyModule ivyModule;
-    private final Project project;
     private boolean includeAllConfigurations;
     private Set<String> includedConfigurations;
     private Set<String> optionalConfigurations;
     private Set<String> externalConfigurations;
 
-    IvyModuleComponentResolver(Component.IvyModule ivyModule, Project project) 
{
-        this.ivyModule = ivyModule;
-        this.project = project;
+    /**
+     * Sets the configurations to include in the SBOM.
+     *
+     * <p>Defaults to the configurations resolved by the last resolve call, or 
{@code *} if no resolve was explicitly
+     * called</p>
+     *
+     * @param comma separated list of the configurations to retrieve or {@code 
*}.
+     */
+    public void setConf(String conf) {
+        this.conf = conf;
+    }
+
+    /**
+     * Sets the id which was used for a previous resolve.
+     *
+     * <p>Defaults to {@code [org].[module]}.</p>
+     *
+     * @param id which was used for a previous resolve
+     */
+    public void setResolveId(String resolveId) {
+        this.resolveId = resolveId;
+    }
+
+    /**
+     * Sets a reference is a different Ivy settings file than the default 
shall be used.
+     *
+     * <p>Defaults to {@code ivy.instance}.</p>
+     *
+     * @param ref A reference to Ivy settings that must be used by this 
component
+     */
+    public void setSettingsRef(Reference ref) {
+        settingRef = ref;
+    }
+
+    /**
+     * Marks configurations as optional.
+     *
+     * <p>Any module that is included in the SBOM because it is required by on 
of the configurations given in {@link
+     * #setConf} and only is included because of configurations listed here is 
marked optional. Including configurations
+     * that are not part of {@link #setConf} doesn't have any effect. {@code 
*} is no supported. The default is to have
+     * no optional components.</p>
+     *
+     * @param comma separated list of the configurations to mark optional.
+     */
+    public void setOptionalConf(String optionalConf) {
+        this.optionalConf = optionalConf;
+    }
+
+    /**
+     * Marks configurations as external.
+     *
+     * <p>Any module that is included in the SBOM because it is required by on 
of the configurations given in {@link
+     * #setConf} and only is included because of configurations listed here is 
marked external. Including configurations
+     * that are not part of {@link #setConf} doesn't have any effect. {@code 
*} is no supported. The default is to have
+     * no external components.</p>
+     *
+     * @param comma separated list of the configurations to mark external.
+     */
+    public void setExternalConf(String externalConf) {
+        this.externalConf = externalConf;
+    }
+
+    /**
+     * The retrieve pattern used for retrieving the dependency artifacts.
+     *
+     * <p>Defaults to {@code ${ivy.retrieve.pattern}}.</p>
+     */
+    public void setPattern(String pattern) {
+        this.pattern = pattern;
+    }
+
+    /**
+     * Adds a nested template component.
+     *
+     * @param c nested template component
+     */
+    public void addConfiguredTemplateComponent(Component c) {
+        templateComponents.put(getTemplateComponentKey(c), c);
     }
 
     /**
@@ -109,19 +192,18 @@ class IvyModuleComponentResolver {
     }
 
     private Ivy createIvyInstance(Component component) {
-        Reference settingRef = ivyModule.getSettingsRef();
         IvyAntSettings engine;
         if (settingRef == null) {
             engine = IvyAntSettings.getDefaultInstance(component);
         }
         else {
-            engine = settingRef.getReferencedObject(project);
+            engine = settingRef.getReferencedObject(getProject());
         }
         return engine.getConfiguredIvyInstance(component);
     }
 
     private void parseConfigurations(IvySettings settings) {
-        String conf = ivyModule.getConf();
+        String conf = this.conf;
         if (conf == null || "*".equals(conf)) {
             conf = settings.getVariable("ivy.resolved.configurations");
         }
@@ -134,8 +216,8 @@ class IvyModuleComponentResolver {
         } else {
             includedConfigurations = confAsSet(conf);
         }
-        optionalConfigurations = confAsSet(ivyModule.getOptionalConf());
-        externalConfigurations = confAsSet(ivyModule.getExternalConf());
+        optionalConfigurations = confAsSet(optionalConf);
+        externalConfigurations = confAsSet(externalConf);
     }
 
     private ResolveReport loadResolveReport(IvySettings settings) {
@@ -159,15 +241,14 @@ class IvyModuleComponentResolver {
     }
 
     private ResolveReport getResolvedReport(String org, String module) {
-        String resolveId = ivyModule.getResolveId();
         ResolveReport report;
         if (resolveId != null) {
-            report = project.getReference("ivy.resolved.report." + resolveId);
+            report = getProject().getReference("ivy.resolved.report." + 
resolveId);
         } else {
-            report = project.getReference("ivy.resolved.report." + org + "." + 
module);
+            report = getProject().getReference("ivy.resolved.report." + org + 
"." + module);
         }
         if (report == null) {
-            report = project.getReference("ivy.resolved.report");
+            report = getProject().getReference("ivy.resolved.report");
         }
         return report;
     }
@@ -178,9 +259,9 @@ class IvyModuleComponentResolver {
                                   Set<ModuleRevisionId> externalModules,
                                   Map<ModuleRevisionId, File> componentFiles) {
         ModuleRevisionId mrid = md.getModuleRevisionId();
-        Component template = 
ivyModule.getTemplateComponents().get(mrid.getOrganisation() + ":" + 
mrid.getName());
+        Component template = templateComponents.get(mrid.getOrganisation() + 
":" + mrid.getName());
         Component c = template == null ? new Component() : new 
Component(template);
-        c.setProject(project);
+        c.setProject(getProject());
         fillFromModuleDescriptor(c, md, dependencyTree);
 
         if (optionalModules.contains(mrid)) {
@@ -328,18 +409,18 @@ class IvyModuleComponentResolver {
     private Map<ModuleRevisionId, File> findDownloadedArtifacts(Ivy ivy,
                                                                 IvySettings 
settings,
                                                                 
ModuleRevisionId mrid) {
-        String pattern = ivyModule.getPattern();
+        String pattern = this.pattern;
         if (pattern == null) {
             pattern = settings.getVariable("ivy.retrieve.pattern");
         }
         if (pattern == null) {
-            project.log("no retrieve pattern, won't look for dependency 
artifact files");
+            log("no retrieve pattern, won't look for dependency artifact 
files");
             return Collections.emptyMap();
         }
 
         RetrieveEngine retrieveEngine = ivy.getRetrieveEngine();
         if (retrieveEngine == null) {
-            project.log("no RetrieveEngine, won't look for dependency artifact 
files");
+            log("no RetrieveEngine, won't look for dependency artifact files");
             return Collections.emptyMap();
         }
 
@@ -348,7 +429,6 @@ class IvyModuleComponentResolver {
             ? new String[] { "*" }
             : includedConfigurations.toArray(new 
String[includedConfigurations.size()]);
         opts.setConfs(confs);
-        String resolveId = ivyModule.getResolveId();
         if (resolveId != null) {
             opts.setResolveId(resolveId);
         }
@@ -366,7 +446,7 @@ class IvyModuleComponentResolver {
             }
             return result;
         } catch (ParseException | IOException e) {
-            project.log(e.getMessage(), Project.MSG_ERR);
+            log(e.getMessage(), Project.MSG_ERR);
             throw new BuildException("syntax errors in ivy file: " + e, e);
         }
     }
@@ -408,4 +488,16 @@ class IvyModuleComponentResolver {
         ModuleRevisionId mrid = n.getId();
         return "pkg:maven/" + mrid.getOrganisation() + "/" + mrid.getName() + 
"@" + mrid.getRevision() + "?type=jar";
     }
+
+    private static String getTemplateComponentKey(Component c) {
+        String group = c.getGroup();
+        if (group == null) {
+            group = "";
+        }
+        String name = c.getName();
+        if (name == null) {
+            name = "";
+        }
+        return group + ":" + name;
+    }
 }
diff --git a/src/main/org/apache/ant/cyclonedx/SbomLinkComponentResolver.java 
b/src/main/org/apache/ant/cyclonedx/SbomLinkComponentResolver.java
index d6f06f5..a1d877d 100644
--- a/src/main/org/apache/ant/cyclonedx/SbomLinkComponentResolver.java
+++ b/src/main/org/apache/ant/cyclonedx/SbomLinkComponentResolver.java
@@ -30,6 +30,7 @@ import org.apache.tools.ant.BuildException;
 import org.apache.tools.ant.Project;
 import org.apache.tools.ant.types.Resource;
 import org.apache.tools.ant.types.resources.FileProvider;
+import org.apache.tools.ant.types.resources.Union;
 import org.apache.tools.ant.types.resources.URLProvider;
 
 import org.cyclonedx.exception.ParseException;
@@ -42,13 +43,23 @@ import org.cyclonedx.parsers.Parser;
  *
  * @since CycloneDX Antlib 0.2
  */
-class SbomLinkComponentResolver {
-    private final Project project;
-    private final Component.SbomLink sbomLink;
+public class SbomLinkComponentResolver extends Union implements 
ComponentResolver {
+    private boolean createBomExternalReference = true;
 
-    SbomLinkComponentResolver(Project project, Component.SbomLink sbomLink) {
-        this.project = project;
-        this.sbomLink = sbomLink;
+    /**
+     * Whether to create a bom-Type external reference in the
+     * resolved component based on the nested resource's URI.
+     *
+     * <p>Will not create an external reference of there are
+     * already external references om the component or the
+     * resolved SBOM already contains a bom-type reference.</p>
+     *
+     * <p>Defaults to <code>true</code>.
+     *
+     * @param create whether to create a bom-Type external reference
+     */
+    public void setCreateBomExternalReference(boolean create) {
+        createBomExternalReference = create;
     }
 
     /**
@@ -58,7 +69,7 @@ class SbomLinkComponentResolver {
      * @param parent component the link applies to
      * @return Components that are direct dependencies of the parent component
      */
-    Collection<Component> resolve(Component parent) throws IOException, 
BuildException {
+    public Collection<Component> resolve(Component parent) throws IOException, 
BuildException {
         Bom bom = readLinkedSbom();
         if (bom.getMetadata() == null) {
             throw new BuildException("referenced SBOM file lacks metadata");
@@ -78,10 +89,10 @@ class SbomLinkComponentResolver {
             parent.fillFrom(real, Collections.emptyList());
         }
 
-        if (sbomLink.getCreateBomExternalReference()
+        if (createBomExternalReference
             && !parent.getExternalReferences().stream()
             .anyMatch(e -> 
e.getType().equals(org.cyclonedx.model.ExternalReference.Type.BOM))) {
-            Resource sbom = sbomLink.iterator().next();
+            Resource sbom = iterator().next();
             URLProvider up = sbom.as(URLProvider.class);
             if (up != null) {
                 ExternalReference e = new ExternalReference();
@@ -102,10 +113,10 @@ class SbomLinkComponentResolver {
     }
 
     private Bom readLinkedSbom() throws IOException {
-        if (sbomLink.size() != 1) {
+        if (size() != 1) {
             throw new BuildException("sbomLink requires exactly one nested 
resource");
         }
-        Resource sbom = sbomLink.iterator().next();
+        Resource sbom = iterator().next();
         logSbom(sbom);
         try (InputStream data = sbom.getInputStream();
              ByteArrayOutputStream baos = new ByteArrayOutputStream()) {
@@ -136,7 +147,7 @@ class SbomLinkComponentResolver {
                 name = up.getURL().toExternalForm();
             }
         }
-        project.log("reading SBOM from " + name, Project.MSG_VERBOSE);
+        log("reading SBOM from " + name, Project.MSG_VERBOSE);
     }
 
     private List<Component> 
extractComponentsThatAreDirectDependencies(Component parent,
diff --git a/src/main/org/apache/ant/cyclonedx/antlib.xml 
b/src/main/org/apache/ant/cyclonedx/antlib.xml
index d773e30..63df8d1 100644
--- a/src/main/org/apache/ant/cyclonedx/antlib.xml
+++ b/src/main/org/apache/ant/cyclonedx/antlib.xml
@@ -83,4 +83,13 @@ under the License.
       </cdx:componentbom>
     </sequential>
   </macrodef>
+
+  <!-- ComponentResolvers -->
+  <componentdef
+      name="sbomLink"
+      classname="org.apache.ant.cyclonedx.SbomLinkComponentResolver"/>
+  <componentdef
+      name="ivyModule"
+      classname="org.apache.ant.cyclonedx.IvyModuleComponentResolver"/>
+
 </antlib>
diff --git a/src/tests/antunit/component-ivymodule-test.xml 
b/src/tests/antunit/component-ivymodule-test.xml
index 1ffad58..bdb92d0 100644
--- a/src/tests/antunit/component-ivymodule-test.xml
+++ b/src/tests/antunit/component-ivymodule-test.xml
@@ -39,7 +39,7 @@
       <cdx:componentbom outputdirectory="${output}" format="xml"
                         xmlns:cdx="antlib:org.apache.ant.cyclonedx">
         <component>
-          <ivymodule/>
+          <cdx:ivyModule/>
         </component>
       </cdx:componentbom>
     </au:expectfailure>
@@ -51,7 +51,7 @@
     <cdx:componentbom outputdirectory="${output}" format="xml"
                       xmlns:cdx="antlib:org.apache.ant.cyclonedx">
       <component>
-        <ivymodule/>
+        <cdx:ivyModule/>
       </component>
     </cdx:componentbom>
     <xmlproperty file="${output}/bom.xml"/>
@@ -146,7 +146,7 @@
     <cdx:componentbom outputdirectory="${output}" format="xml"
                       xmlns:cdx="antlib:org.apache.ant.cyclonedx">
       <component>
-        <ivymodule conf="default"/>
+        <cdx:ivyModule conf="default"/>
       </component>
     </cdx:componentbom>
     <xmlproperty file="${output}/bom.xml"/>
@@ -168,7 +168,7 @@
     <cdx:componentbom outputdirectory="${output}" format="xml"
                       xmlns:cdx="antlib:org.apache.ant.cyclonedx">
       <component>
-        <ivymodule optionalconf="ant"/>
+        <cdx:ivyModule optionalconf="ant"/>
       </component>
     </cdx:componentbom>
     <xmlproperty file="${output}/bom.xml"/>
@@ -188,7 +188,7 @@
     <cdx:componentbom outputdirectory="${output}" format="xml"
                       xmlns:cdx="antlib:org.apache.ant.cyclonedx">
       <component>
-        <ivymodule externalconf="ant"/>
+        <cdx:ivyModule externalconf="ant"/>
       </component>
     </cdx:componentbom>
     <xmlproperty file="${output}/bom.xml"/>
@@ -211,7 +211,7 @@
     <cdx:componentbom outputdirectory="${output}" format="xml"
                       xmlns:cdx="antlib:org.apache.ant.cyclonedx">
       <component>
-        <ivymodule resolveId="foo-bar-id"/>
+        <cdx:ivyModule resolveId="foo-bar-id"/>
       </component>
     </cdx:componentbom>
     <xmlproperty file="${output}/bom.xml"/>
@@ -227,7 +227,7 @@
     <cdx:componentbom outputdirectory="${output}" format="xml"
                       xmlns:cdx="antlib:org.apache.ant.cyclonedx">
       <component description="my description">
-        <ivymodule/>
+        <cdx:ivyModule/>
         <manufacturer name="Example">
           <url url="https://example.org/"/>
         </manufacturer>
@@ -260,7 +260,7 @@
     <cdx:componentbom outputdirectory="${output}" format="xml"
                       xmlns:cdx="antlib:org.apache.ant.cyclonedx">
       <component>
-        <ivymodule/>
+        <cdx:ivyModule/>
       </component>
       <additionalComponent
         name="ant"
@@ -340,7 +340,7 @@
     <cdx:componentbom outputdirectory="${output}" format="xml"
                       xmlns:cdx="antlib:org.apache.ant.cyclonedx">
       <component>
-        <ivymodule>
+        <cdx:ivyModule>
           <templateComponent
               name="ant"
               group="org.apache.ant"
@@ -349,7 +349,7 @@
               id="ant">
             <manufacturer refid="ant-team"/>
           </templateComponent>
-        </ivymodule>
+        </cdx:ivyModule>
       </component>
     </cdx:componentbom>
     <copy todir="/tmp" file="${output}/bom.xml"/>
diff --git a/src/tests/antunit/component-sbomlink-test.xml 
b/src/tests/antunit/component-sbomlink-test.xml
index c75a2a0..d7a6a2e 100644
--- a/src/tests/antunit/component-sbomlink-test.xml
+++ b/src/tests/antunit/component-sbomlink-test.xml
@@ -26,9 +26,9 @@
         format="xml"
         xmlns:cdx="antlib:org.apache.ant.cyclonedx">
       <component>
-        <sbomLink>
+        <cdx:sbomLink>
           <file file="${output}/bom.json"/>
-        </sbomLink>
+        </cdx:sbomLink>
       </component>
     </cdx:componentbom>
     <xmlproperty file="${output}/merged.xml"/>
@@ -165,9 +165,9 @@
         format="xml"
         xmlns:cdx="antlib:org.apache.ant.cyclonedx">
       <component>
-        <sbomLink>
+        <cdx:sbomLink>
           <url 
url="https://repo1.maven.org/maven2/org/apache/ant/ant-cyclonedx/0.1/ant-cyclonedx-0.1-cyclonedx.json"/>
-        </sbomLink>
+        </cdx:sbomLink>
       </component>
     </cdx:componentbom>
     <xmlproperty file="${output}/merged.xml"/>
@@ -188,9 +188,9 @@
         format="xml"
         xmlns:cdx="antlib:org.apache.ant.cyclonedx">
       <component>
-        <sbomLink createBomExternalReference="false">
+        <cdx:sbomLink createBomExternalReference="false">
           <url 
url="https://repo1.maven.org/maven2/org/apache/ant/ant-cyclonedx/0.1/ant-cyclonedx-0.1-cyclonedx.json"/>
-        </sbomLink>
+        </cdx:sbomLink>
       </component>
     </cdx:componentbom>
     <xmlproperty file="${output}/merged.xml"/>
@@ -207,9 +207,9 @@
         format="xml"
         xmlns:cdx="antlib:org.apache.ant.cyclonedx">
       <component>
-        <sbomLink>
+        <cdx:sbomLink>
           <url 
url="https://repo1.maven.org/maven2/org/apache/ant/ant-cyclonedx/0.1/ant-cyclonedx-0.1-cyclonedx.json"/>
-        </sbomLink>
+        </cdx:sbomLink>
         <externalReference type="bom" url="https://example.org/"/>
       </component>
     </cdx:componentbom>
@@ -233,9 +233,9 @@
         format="xml"
         xmlns:cdx="antlib:org.apache.ant.cyclonedx">
       <component supplierIsManufacturer="true">
-        <sbomLink>
+        <cdx:sbomLink>
           <file file="${output}/bom.json"/>
-        </sbomLink>
+        </cdx:sbomLink>
       </component>
     </cdx:componentbom>
     <xmlproperty file="${output}/merged.xml"/>
@@ -290,9 +290,9 @@
         <tag>label2</tag>
         <property name="xyzzy" value="baz"/>
         <dependency bomRef="my-own-dependency"/>
-        <sbomLink>
+        <cdx:sbomLink>
           <file file="${output}/bom.json"/>
-        </sbomLink>
+        </cdx:sbomLink>
       </component>
       <additionalComponent name="my-own-dependency" 
bomRef="my-own-dependency"/>
     </cdx:componentbom>
@@ -441,9 +441,9 @@
         format="xml"
         xmlns:cdx="antlib:org.apache.ant.cyclonedx">
       <component>
-        <sbomLink>
+        <cdx:sbomLink>
           <file file="${output}/bom.json"/>
-        </sbomLink>
+        </cdx:sbomLink>
       </component>
       <additionalComponent
           name="commons-codec"
diff --git a/src/tests/antunit/component-test.xml 
b/src/tests/antunit/component-test.xml
index 75bd7c8..5a757b4 100644
--- a/src/tests/antunit/component-test.xml
+++ b/src/tests/antunit/component-test.xml
@@ -756,7 +756,7 @@
         xmlns:au="antlib:org.apache.ant.antunit">
       <cdx:component refid="foo"
                      xmlns:cdx="antlib:org.apache.ant.cyclonedx">
-        <sbomLink/>
+        <cdx:sbomLink/>
       </cdx:component>
     </au:expectfailure>
     <au:expectfailure
@@ -764,7 +764,7 @@
         xmlns:au="antlib:org.apache.ant.antunit">
       <cdx:component refid="foo"
                      xmlns:cdx="antlib:org.apache.ant.cyclonedx">
-        <ivymodule/>
+        <cdx:ivyModule/>
       </cdx:component>
     </au:expectfailure>
   </target>
@@ -772,11 +772,11 @@
   <target
       name="testSbomLinkAndIvyModuleAreMutuallyExclusive">
     <au:expectfailure
-        expectedMessage='sbomLink and ivyModule are mutually exclusive'
+        expectedMessage='only one resolver can be used at a time'
         xmlns:au="antlib:org.apache.ant.antunit">
       <cdx:component xmlns:cdx="antlib:org.apache.ant.cyclonedx">
-        <sbomLink/>
-        <ivymodule/>
+        <cdx:sbomLink/>
+        <cdx:ivyModule/>
       </cdx:component>
     </au:expectfailure>
   </target>

Reply via email to