Update `NestedJarFileResources` to fix a race condition that could
result in a `NullPointerException`.
The methods `getOrCreateInflater` and `endOrCacheInflater` both
synchronize on a local `inflaterCache` variable but attempted to use the
`this.inflaterCache` instance which could be set to null.
The `releaseInflators` method had a synchronized block for ending each
inflater but the cache was being nulled out outside the block meaning that
the other 2 methods mentioned could hit the race condition.
Signed-off-by: Ian Kettle <25729118+icikle@users.noreply.github.com>
See gh-51744
LayersIndex.Node kept its children in a list and scanned that list
linearly to find the child for each path segment. Building the index is
therefore O(entries x siblings), a cost that is dominated by the largest
flat directory in the jar: BOOT-INF/lib/ for the dependencies, and any
bundled resource directory such as a front-end build output.
Keep the children in a LinkedHashMap keyed by the segment name so that
lookups are constant time. Insertion order is preserved, so the order in
which buildIndex() walks the tree is unchanged.
For a jar with 32,529 entries, 426 dependencies and a 12,000 file static
resource directory, this reduces the number of string comparisons from
73.7M to 222K and the time spent building the index from 197ms to 12ms.
For a jar with no large flat directory (20,529 entries, 426
dependencies) the gain is much smaller: 1.58M comparisons to 162K, and
12.5ms to 9.6ms. The generated layers.idx is byte for byte identical in
both cases.
See gh-51654
Signed-off-by: Junggi Kim <kimjg2477@gmail.com>
IndexedLayers.getLayer() walked every entry of layers.idx for each jar
entry being extracted, making extraction O(entries x index size). The
dependency entries are listed first while the application classes are
covered by a single directory entry near the end, so the most numerous
entries consistently scanned the longest.
Index the candidates once when the index file is read, keeping the
directory candidates in a separate map that preserves index order. A
file name is then resolved with a single map lookup and only the
handful of directory candidates has to be scanned.
For a jar with 24,823 entries and 700 dependencies this reduces the
number of string comparisons from 17,179,582 to 72,946 (106.9ms to
2.5ms). With 4,173 entries it goes from 1,019,857 to 11,896 (5.4ms to
0.1ms) and with 903 entries from 70,612 to 2,426 (0.8ms to 0.1ms).
Lookups resolve to the same layer as before. LayersIndex only writes a
name once its whole subtree belongs to a single layer, so no indexed
name is a prefix of another and at most one candidate can match, which
makes the order the two kinds of candidate are consulted in immaterial.
See gh-51653
Signed-off-by: Junggi Kim <kimjg2477@gmail.com>
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Packager#isCycloneDxBom only recognized CycloneDX SBOMs beneath
META-INF/sbom/. In a war, the CycloneDX Maven plugin's output is
packaged beneath WEB-INF/classes/META-INF/sbom/, so repackaging did
not add the SBOM manifest attributes.
Also check beneath Layout#getClassesLocation(), which findMainMethod
already uses to locate application classes. Continue to support the
root location used by jars.
Signed-off-by: COBI-98 <tkdgus968@naver.com>
See gh-51551
Add an 'sbom' command to the tools jar mode which prints the SBOM
packaged in an uber jar or war. The SBOM is located using the
Sbom-Location manifest attribute and its bytes are copied verbatim to
the console, or to the file given by --destination.
See gh-51505
Signed-off-by: Hyeongjun Cho <ryuu.public@gmail.com>