- Primary Language: Java 21
- Framework: Quarkus 3.31.3
- Integration: Apache Camel for Quarkus (routing and integration patterns)
- REST: JAX-RS / Jakarta RESTful Web Services with Jackson
- ORM: Hibernate ORM with Panache
- Database: PostgreSQL with Flyway migrations
- Cache: Redis
- Build Tool: Maven
- Monitoring: Sentry 7.8.0
- SBOM: CycloneDX and SPDX libraries
- Formatter: Spotless Maven Plugin with Google Java Format (GOOGLE style,
reflowLongStringsenabled) - Indentation: 4 spaces per tab
- Import order:
java|javax, org, com, io - Unused imports: Automatically removed
- License header: Apache 2.0 required on all Java files (
src/license/java_header.txt)- Format:
Copyright 2023-2025 Trustify Dependency Analytics Authors
- Format:
- Formatter toggle:
// fmt:off/// fmt:onto disable formatting (used in Camel DSL code) - Encoding: UTF-8
- Line endings: LF, trim trailing whitespace, final newline
- Method length: Methods should not exceed ~40 lines of logic. When a method grows beyond this, extract cohesive blocks into well-named private helpers. Duplicated blocks across methods must always be extracted.
- Packages:
io.github.guacsec.trustifyda.<feature>(domain-driven)- Subpackages:
integration.providers,integration.licenses,integration.backend,integration.sbom,integration.registry
- Subpackages:
- Classes: PascalCase
- Services:
*Service(ModelCardService,CacheService,SpdxLicenseService) - Repositories:
*Repository(ModelCardRepository,GuardrailRepository) - Route Builders:
*Integration(ExhortIntegration,LicensesIntegration,Pep691Integration) - Response Handlers:
*ResponseHandler(TrustifyResponseHandler,HardenedImageResponseHandler,DepsDevResponseHandler) - Providers:
*Provider(VulnerabilityProvider,HardenedImageProvider) - Enrichment Services:
*EnrichmentService— stateless helpers instantiated directly, not CDI-managed (e.g.,RegistryEnrichmentService) - Exceptions:
*Exception(DetailedException,SbomValidationException) - Utility: Private constructor,
finalclass (e.g.,ExceptionUtils)
- Services:
- Methods: camelCase, verb-first (
get*,find*,process*,validate*,is*) - Constants: SCREAMING_SNAKE_CASE (
PROVIDERS_PARAM,TRUSTIFY_TOKEN_HEADER,CYCLONEDX_MEDIATYPE_JSON) - HTTP headers: lowercase-with-hyphens (
ex-trustify-token,ex-request-id) - Query parameters: camelCase (
providers,recommend,verbose) - Records: Used for immutable data (
PackageItem,DependencyTree)
src/main/java/io/github/guacsec/trustifyda/
├── config/ # Configuration and exceptions
│ ├── exception/ # Exception hierarchy
│ └── metrics/
├── integration/
│ ├── backend/ # REST routing (Camel)
│ ├── cache/ # Redis, cache services
│ ├── licenses/ # License integration (deps.dev)
│ ├── lock/ # Distributed locking (LockService)
│ ├── providers/ # Vulnerability providers
│ │ └── trustify/
│ │ └── hardened/ # Hardened image recommendation provider
│ ├── report/ # Report generation
│ ├── registry/ # Ecosystem registry integrations (PEP 691, etc.)
│ ├── sbom/ # SBOM parsing
│ │ ├── cyclonedx/
│ │ └── spdx/
│ └── Constants.java
├── model/ # Domain models
│ ├── trustify/
│ ├── modelcards/
│ ├── registry/ # Registry response records (Pep691Response, etc.)
│ └── licenses/
├── modelcards/ # Model card service layer
├── monitoring/ # Sentry, observability
└── service/ # Service classes
src/main/resources/
├── application.properties
├── db/migration/ # Flyway SQL migrations
├── freemarker/templates/ # Freemarker report templates
│ ├── report.ftl # Main HTML report template
│ └── generated/ # Compiled React assets (do not edit manually)
└── license-categories.yaml
ui/ # React frontend for HTML report
├── src/ # TypeScript/React source code
├── craco.config.js # Webpack configuration override
├── package.json # Dependencies and build scripts
└── tsconfig.json
- Feature/capability-based organization, not layered
- Integration routes separated from business logic
- Required properties: Use plain
Stringor typed field. Quarkus throwsDeploymentExceptionat startup if the value is missing. - Optional properties: Use
Optional<String>withoutdefaultValue. This allows the application to start when the environment variable is unset. Do not useStringwithdefaultValue = ""— it prevents distinguishing "unconfigured" from "explicitly empty". Example:Check with@ConfigProperty(name = "api.pypi.registry.host") Optional<String> registryHost;
registryHost.isPresent() && !registryHost.get().isBlank(). - Timeout properties: Use
Stringtype with a duration suffix (e.g.,"10s"), passed to Camel fault tolerance configuration.
When a feature needs to support multiple ecosystem implementations (e.g., registry lookups for pypi, maven, npm), use CDI Instance<T> discovery:
- Define a package-private interface (not public) with
isEnabled()and the operation method:interface RegistryIntegration { boolean isEnabled(); void enrich(AnalysisReport report, DependencyTree tree); }
- Implement per-ecosystem as
@ApplicationScopedbeans extendingEndpointRouteBuilderand implementing the interface. Each implementation owns its own Camel routes and config properties. - Orchestrate via
Instance<T>in a single orchestrator class that iterates all discovered implementations, calls only enabled ones, and isolates exceptions:@Inject Instance<RegistryIntegration> registryIntegrations;
- Keep Camel concerns out of the interface — the interface methods accept domain objects (
AnalysisReport,DependencyTree), notExchange. The orchestrator handles Exchange extraction. - Run sequentially — enrichment services mutate shared report structures that are not thread-safe.
- Adding a new ecosystem requires only one new class implementing the interface. No changes to the orchestrator or main route.
For reusable business logic shared across multiple CDI beans (e.g., report enrichment), use package-private stateless classes instantiated directly (not CDI-managed):
class RegistryEnrichmentService {
void enrichReport(AnalysisReport report, DependencyTree tree,
String packagePrefix,
BiFunction<String, String, Optional<PackageRef>> registryQuery) { ... }
}Instantiate in the field initializer of the owning bean: private final RegistryEnrichmentService enrichmentService = new RegistryEnrichmentService();
Use this pattern when the helper has no injected dependencies and serves as a pure function container. If the helper needs CDI injection, make it @ApplicationScoped instead.
Redis access is centralized behind service interfaces. No class should inject RedisDataSource directly — always go through the appropriate service interface.
For caching provider responses and license data. Interface defines cacheItems(), getCachedItems(), cacheLicenses(), getCachedLicenses(). The RedisCacheService implementation uses typed ValueCommands with TTL-based expiry (psetex).
For distributed locking across replicas (e.g., preventing concurrent refresh jobs). Interface defines tryAcquire(key, ttl) and release(key). The RedisLockService implementation uses atomic SET NX EX GET via setGet() for race-free acquisition and ownership-checked release.
public interface LockService {
boolean tryAcquire(String key, Duration ttl);
void release(String key);
}For integrations that periodically fetch and cache external data (as opposed to request-driven Camel routes), use the background provider pattern:
@ApplicationScopedprovider class with@Scheduledrefresh method- Quarkus REST client interface (built via
QuarkusRestClientBuilder) for HTTP calls — Camel routes are not appropriate for background jobs - Dedicated
*ResponseHandlerclass (@ApplicationScoped) for response parsing LockServicefor distributed locking across replicas- Thread-safe in-memory index (volatile reference swap) for lookups
@ConfigMappinginterface for provider configuration (URL, refresh interval, lock TTL)- Constructor injection for all dependencies, with a package-private constructor accepting a mock HTTP client for testing
Example: HardenedImageProvider + HardenedImageResponseHandler + HummingbirdClient + HardenedImageRecommendation
This pattern differs from Camel-based integrations because:
- No user request triggers the fetch — it runs on a timer
- No Exchange/Message plumbing is needed
- Circuit breakers are replaced by try/catch + index preservation on failure
- Exception hierarchy:
DetailedException(base) — runtime exception with optional details and status codeClientDetailedException— client-facing errorsSbomValidationException,CycloneDXValidationException,SpdxValidationExceptionPackageValidationException,UnexpectedProviderException
- Camel exception handling:
onException(TimeoutException.class).handled(true).process(responseHandler::processResponseError) - HTTP status mapping: 400 (validation), 401 (unauthorized), 422 (unsupported provider), 504 (timeout), 500 (internal)
- Error responses:
text/plainfor validation errors, JSON for complex errors, all includeex-request-idheader - Utilities:
ExceptionUtils.findInChain(),ExceptionUtils.getLongestMessage()
- Circuit breaker: Use MicroProfile Fault Tolerance via Camel for external HTTP calls. Configure
timeoutEnabled(true)andtimeoutDuration()from a config property. - Fallback handling: Define
.onFallback().process(this::handleLookupFallback)to return a safe default (e.g., 504 status, null body) when a circuit breaker trips. - HTTP header cleanup: Before making outbound HTTP calls, remove stale headers (
HTTP_RAW_QUERY,HTTP_QUERY,HTTP_URI,HTTP_PATH,HTTP_HOST,ACCEPT_ENCODING,CONTENT_TYPE) to prevent header leakage between requests. - Route naming: Route IDs must match the method/direct endpoint name (e.g.,
direct("pep691Lookup")→.routeId("pep691Lookup")). - Dynamic URLs: Use
.toD("${exchangeProperty.propertyName}?throwExceptionOnFailure=false")for URLs resolved at runtime from exchange properties. - Single entry point: The main analysis route (
ExhortIntegration) callsdirect:enrichTrustedLibrariesas a single entry point. The orchestrator (TrustedLibrariesIntegration) discovers and runs all registry integrations. Never add ecosystem-specific routes directly to the main analysis route.
Every Camel route that makes external HTTP requests must be instrumented with ProviderRoutePolicy so it appears in the Grafana provider dashboards. This applies to vulnerability providers, license providers, registry integrations, and any future integration that calls an external service.
- Inject
MeterRegistryinto the route builder class:@Inject MeterRegistry registry;
- Attach
ProviderRoutePolicyimmediately after.routeId(...):.routePolicy(new ProviderRoutePolicy(registry))
- Set the provider name on the exchange before the circuit breaker:
.setProperty(Constants.PROVIDER_NAME_PROPERTY, constant("provider-name"))
This produces a camel.route.provider.requests timer with provider and routeId tags, p90/p95/p99 percentiles, and SLO histogram buckets.
| Integration | Route ID | Provider tag | Status |
|---|---|---|---|
| TrustifyIntegration | recommendations, vulnerabilities, trustifyHealthCheck, trustifyValidateCredentials |
trustify |
Instrumented |
| LicensesIntegration | depsDevRequest |
deps.dev |
Instrumented |
| Pep691Integration | pep691Lookup |
pypi |
Instrumented |
None — all external-facing Camel routes are instrumented.
Background scheduled providers (e.g., HardenedImageProvider) do not use Camel routes and therefore do not use ProviderRoutePolicy. If metrics are needed for background HTTP calls, use Micrometer Timer directly.
- Frameworks: JUnit 5 (Jupiter), Quarkus Test, Mockito (via
quarkus-junit5-mockito) - HTTP testing: REST Assured for API tests, WireMock 3.4.2 for mocking external services
- HTML validation: HTMLUnit 4.11.1
- Test naming:
*Test.java(unit),*IT.java(integration) - Annotations:
@QuarkusTest,@QuarkusTestResource,@ParameterizedTest,@Nested - Custom extensions:
WiremockExtension,OidcWiremockExtension,@InjectWireMock - REST Assured pattern:
given().header(...).body(...).when().post(...).then().assertThat().statusCode(...) - Cache testing: Two-request pattern to verify cache hits;
server.resetRequests()between tests - Test data: JSON fixtures in
src/test/resources/{format}/(e.g.,pypi-registry/,depsdev/,trustify/,reports/) - Test comments: Use
//(regular line comments) for test method descriptions, not///(Java 23 markdown doc comments) or/** */Javadoc. The project targets Java 21. - Assertions: JUnit static imports + Hamcrest matchers
- Unit testing CDI beans: For non-Quarkus unit tests, instantiate beans directly and set
@Inject/@ConfigPropertyfields manually (package-private visibility). Mock CDIInstance<T>with Mockito. - Unit testing Camel routes: For route builder tests that don't need full Camel context, test the
process()methods directly by constructing mockExchangeandMessageobjects. - Integration testing with WireMock: Register WireMock stubs for external registries in test setup. Use
AbstractAnalysisTesthelpers likereplaceMockedRegistryUrl()to inject WireMock URLs into test fixtures.
When constructing Package URL (PURL) strings with qualifiers:
- URL-encode qualifier values using
URLEncoder.encode(value, StandardCharsets.UTF_8), especially forrepository_urlwhich contains full URLs with://and path separators. - Use
PackageRef.builder().purl(...)pattern for constructing PURL-based references. - Normalize package names for registry lookups: lowercase, replace
-and.with_(PEP 503/PEP 691 normalization for pypi).
- Conventional Commits:
<type>(<scope>): <description> - Release commits:
build(release): <message>(configured in Maven Release Plugin) - Tags:
v<semver>(e.g.,v2.0.0)
The HTML report is a self-contained, single-page React application embedded into a Freemarker template. All CSS and JS assets are inlined so the resulting HTML can be viewed offline or attached to emails.
- React UI (
ui/): Built with React 18 + PatternFly 5, TypeScript, and Craco (webpack wrapper). Yarn 4 is the package manager. - Build pipeline (Maven
frontend-maven-plugin): During thecompilephase, Maven installs Node + corepack and runsyarn install && yarn build. The Craco config splits output intomain.js,vendor.js,main.css, andvendor.css. - Asset copy: The build script copies compiled bundles from
ui/build/static/intosrc/main/resources/freemarker/templates/generated/. - Freemarker template (
src/main/resources/freemarker/templates/report.ftl): Inlines the generated CSS/JS and passes analysis data to the React app viawindow.appData. Uses square-bracket tag syntax ([#if],[=var]). - Camel route (
ReportIntegration): ThehtmlReportdirect route setsContent-Type: text/html, callsReportTemplate.setVariables(), and processes through the Freemarker template. - Trigger: Clients request an HTML report by sending
Accept: text/html(ormultipart/mixedfor both HTML + JSON) to/api/v4/analysisor/api/v5/analysis.
src/main/resources/freemarker/templates/generated/
├── main.js # React application code
├── vendor.js # Third-party dependencies
├── main.css # Application styles
└── vendor.css # PatternFly & dependency styles
These files are committed to the repository. If you change anything under ui/, you must rebuild them before committing (see Pre-Commit Checklist below).
Before committing code, always verify the following:
- Format the code: Run
mvn spotless:applyto auto-format all Java sources (Google Java Format). - Lint and rebuild the UI (if
ui/was modified):- Run
yarn lint(oryarn lint:fix) insideui/to check ESLint + Prettier rules. - Run
yarn buildinsideui/(or letmvn compiledo it) to recompile the React app. - Ensure the regenerated files under
src/main/resources/freemarker/templates/generated/are included in the commit.
- Run
- Run the tests: Execute
mvn verify(or at minimummvn test) and ensure all tests pass.
- BOM pattern: Quarkus BOM + Quarkus Camel BOM imported via
<scope>import</scope> - Version management: All versions in
<properties>section of pom.xml - Quarkus extensions:
quarkus-rest-jackson,quarkus-hibernate-orm-panache,quarkus-oidc-client,quarkus-redis-client - Camel extensions:
camel-quarkus-*(not plain camel) - Native image:
@RegisterForReflectionannotations,nativeMaven profile for GraalVM builds - Build plugins: Spotless, Flatten Maven Plugin, Maven Release Plugin, Frontend Maven Plugin (Node/Yarn for UI)
- Distribution: Maven Central with GPG signing