Backend of the National Data Catalog (NDC) for Semantic Interoperability, a component of the PDND (Piattaforma Digitale Nazionale Dati, Italy's National Digital Data Platform). The service exposes a national catalog of semantic assets — ontologies, controlled vocabularies, schemas — produced by the Italian Public Administration, with automatic harvesting from public repositories, indexing, search, validation, and consultation APIs.
The backend is composed of:
- a harvester that periodically crawls the repositories published by catalog participants, downloading the semantic material and populating indexes and databases;
- a triple store (Virtuoso) queryable via SPARQL, where RDF material is loaded per graph;
- a search index (Elasticsearch) for full-text and semantic searches;
- a REST API (OpenAPI) for search, asset details, controlled vocabularies, on-demand validation, dashboards, and operational management.
- Tech stack
- Prerequisites
- Quick start
- Configuration
- Common commands
- API
- Asset validation
- Harvesting
- Container and deployment
- Code quality and security
- Contributing
- License
| Component | Version | Notes |
|---|---|---|
| Java | 17 | JDK 17 (CI), runtime distroless gcr.io/distroless/java17-debian11:nonroot |
| Spring Boot | 3.5.8 | Web, Data JPA, Data Elasticsearch, Security, Validation, Mail, Actuator |
| Apache Jena | 5.5.0 | RDF parsing/serialization, RIOT, query builder |
| Elasticsearch | 8.14.3 | Full-text and semantic search index |
| OpenLink Virtuoso | tenforce/virtuoso |
SPARQL triple store |
| MySQL | 9.0 | Harvester metadata (jobs, runs, configuration, events) |
| Flyway | core + mysql | Schema migrations |
| Gradle | wrapper | Build, test, lint, scan |
| OpenAPI Generator | 7.7.0 | Generates interfaces from openapi.yaml |
| MapStruct, Lombok | — | DTO ↔ entity mapping, boilerplate reduction |
| Jib | 3.4.3 | Container image builds without a Dockerfile |
| OWASP dependency-check | 12.2.2 | Dependency vulnerability scanning |
| SpotBugs, Checkstyle, JaCoCo | — | Static analysis and coverage |
- JDK 17 (Eclipse Temurin recommended)
- Docker + Docker Compose (for local dependencies: Virtuoso, Elasticsearch, MySQL)
- (Optional) just for one-line commands (
justfileincluded in the repo) - (Optional) GitHub Personal Access Token if you exceed the anonymous GitHub API rate limit during harvesting (see Configuration)
From a shell at the repository root:
# 1. Start local dependencies (Virtuoso, Elasticsearch, MySQL)
just stack
# or: docker compose up -d
# 2. Build the application (without tests)
just build
# or: ./gradlew clean build -x test
# 3. Start the backend (the "local" profile is active by default)
just run
# or: ./gradlew bootRunThe service is reachable at http://localhost:8080. OpenAPI/Swagger UI: http://localhost:8080/swagger-ui/index.html (raw spec at /api/v3/api-docs).
Development credentials (Basic Auth) for the harvester's protected endpoints:
- user:
harv-user - password:
harv-password
(Override via HARVESTER_USER / HARVESTER_PASSWORD.)
The local profile is active by default (spring.profiles.active=local). Main properties live in src/main/resources/application.properties; the most relevant environment variables are listed below.
| Variable | Default | Description |
|---|---|---|
SPRING_DATASOURCE_URL |
jdbc:mysql://localhost:3306/dev_ndc_harvest |
MySQL JDBC URL |
SPRING_DATASOURCE_USERNAME / _PASSWORD |
root / example |
MySQL credentials |
ELASTICSEARCH_HOST / _PORT / _SCHEME |
localhost / 9200 / https |
Elasticsearch endpoint |
ELASTICSEARCH_USERNAME / _PASSWORD |
elastic / changeme |
Elasticsearch credentials |
VIRTUOSO_SPARQL |
— | Virtuoso SPARQL endpoint (e.g. http://localhost:8890/sparql) |
VIRTUOSO_SPARQL_GRAPH_STORE |
— | SPARQL Graph Store endpoint |
VIRTUOSO_USERNAME / _PASSWORD |
dba / dba |
Virtuoso credentials |
| Variable | Default | Description |
|---|---|---|
HARVESTER_USER / HARVESTER_PASSWORD |
harv-user / harv-password |
Basic Auth for operational endpoints |
HARVESTER_REPOSITORIES |
— | Comma-separated list of repository URLs to harvest |
HARVESTER_ENDPOINT_ENABLED |
— | Enables harvest trigger endpoints |
harvester.folder.skip-words |
scriptR2RML,sparql,deprecated |
Folders to skip (contains match, min 3 chars) |
harvester.ontology.scanner.skip-words |
aligns,example |
Ontologies to skip |
harvester.controlled-vocabulary.scanner.skip-words |
transparency-obligation-organization,transparency-obligation-administration |
Controlled vocabularies to skip |
GITHUB_PERSONAL_ACCESS_TOKEN |
— | GitHub PAT to bypass the public API rate limit (cookiecutter lookup, etc.) |
Verifies that every harvested repository conforms to the official template teamdigitale/dati-semantic-cookiecutter.
| Variable | Default |
|---|---|
HARVESTER_CONFORMANCE_ENABLED |
true |
HARVESTER_CONFORMANCE_COOKIECUTTER_REPO |
teamdigitale/dati-semantic-cookiecutter |
HARVESTER_CONFORMANCE_COOKIECUTTER_BRANCH |
main |
The harvester periodically aggregates per-vocabulary SQLite databases into a single vocabularies.db file served over HTTP.
| Variable | Default |
|---|---|
HARVESTER_AGGREGATE_DB_PATH |
/tmp/ndc-vocabularies.db |
HARVESTER_AGGREGATE_DB_WORK_DIR |
/tmp/ndc-csvapis-work |
HARVESTER_AGGREGATE_DB_CACHE_MAX_AGE |
86400 (seconds) |
| Variable | Default |
|---|---|
ALERTER_MAIL_SENDER |
servicedesk-schema@istat.it |
ALERTER_MAIL_SENDER_FIXED_DELAY |
1800000 ms (30 min) |
ALERTER_SMTP_SERVER / _PORT |
mail.smtpbucket.com / 8025 |
ALERTER_SMTP_USER / _PASSWORD |
servicedesk-schema@istat.it / — |
ALERTER_SMTP_AUTH / _STARTTLS / _SSL |
true / true / false |
The complete property list lives in src/main/resources/application.properties.
The project ships with a justfile for the most frequent commands:
just # list available targets
just stack # start Virtuoso, Elasticsearch, MySQL via docker compose
just down # stop the dependencies
just logs # tail the dependency logs
just clean # ./gradlew clean + reset Docker volumes
just build # build without tests
just test # unit tests
just integration-test # integration tests (Testcontainers)
just check # all verification tasks
just lint # checkstyle + spotbugs
just coverage # JaCoCo + threshold verification
just run # start the backend (bootRun)
just openapi # regenerate OpenAPI sources
just docker-build # build a local container image (Jib)
just dep-check # OWASP dependency-checkAll commands can equivalently be invoked via ./gradlew <task> (see build.gradle).
The OpenAPI specification lives at src/main/resources/public/openapi.yaml. Once the backend is running, Swagger UI is available at http://localhost:8080/swagger-ui/index.html.
| Method | Path | Purpose |
|---|---|---|
GET |
/status |
Application health/status |
GET |
/semantic-assets |
Asset search (filters, pagination) |
GET |
/semantic-assets/by-iri |
Asset details by IRI |
GET |
/vocabularies |
List of controlled vocabularies |
GET |
/vocabularies/{agencyId}/{keyConceptId} |
Vocabulary data |
GET |
/vocabularies/{agencyId}/{keyConceptId}/{id} |
Single vocabulary item |
GET |
/check-url |
URL reachability check |
Validation endpoints (see Asset validation)
| Method | Path | Purpose |
|---|---|---|
POST |
/validate |
One-shot asset validation |
POST |
/validate/syntax |
RDF (Turtle) syntax validation |
POST |
/validate/repo/{owner}/{repo} |
Submit a repository validation job |
POST |
/validate/repo/{owner}/{repo}/{revision} |
Same, on a specific revision |
GET |
/validate/repo/{validationId} |
Validation job status |
| Method | Path | Purpose |
|---|---|---|
POST |
/jobs/harvest |
Trigger harvest (requires HARVESTER_ENDPOINT_ENABLED=true) |
GET |
/jobs/harvest/run |
List harvest job runs |
GET |
/jobs/harvest/running |
Currently running harvest jobs |
POST |
/jobs/clear |
Clear data for a given repository URL |
GET |
/harvest/vocabularies.db |
Download the aggregated vocabularies SQLite DB |
GET/POST |
/config/repository |
Manage registered repositories |
GET |
/config/repository/{id}/validation-report |
Validation report for a repository |
GET |
/config/repository/{id}/conformance |
Cookiecutter conformance report |
GET/POST |
/config/{repoId} |
List/set per-repo configuration entries |
PUT/DELETE |
/config/{repoId}/{configKey} |
Update/delete a single config entry |
GET/POST |
/dashboard/... |
Dashboard data (raw, time/count aggregates) |
* |
/event, /profile, /user |
Alerter events, profiles, and users |
The backend exposes a validation pipeline triggered both during harvesting and on demand:
- Syntax validation (RDF/Turtle) — Apache Jena RIOT in streaming mode (zero
Modelallocation). Errors and warnings are returned as structuredline/col/messageentries. Endpoint:POST /validate/syntax. - DCAT-AP_IT metadata validation — checks for
dct:license(ERROR), validadms:status(WARNING), and other mandatory metadata on distributions. - Repository conformance — verifies the repository conforms to the
teamdigitale/dati-semantic-cookiecuttertemplate: presence of CI workflows,.pre-commit-config.yaml, expected folder structure. Outcome persisted onHARVESTER_RUN.CONFORMANCE_REPORT. - Asynchronous repository validation —
POST /validate/repo/{owner}/{repo}accepts a job and returns avalidationId; the client pollsGET /validate/repo/{validationId}until status isCOMPLETEDorFAILED. Seejust validate-repo-waitfor an end-to-end example.
All outcomes converge into a unified ValidationReport returned by the endpoints above.
The harvester clones each registered repository (via JGit) and runs a sequence of stages tracked in the logs:
- Clone / fetch of the repository (with branch and revision support).
SYNTAX_VALIDATION— RDF parsing; assets with invalid syntax are skipped without aborting the harvest run.- Folder/ontology/vocabulary scan — applying the configured
skip-words. CONFORMANCE_CHECK— cookiecutter template verification (lazy-loading reference files via the GitHub API, optionally with a PAT).- RDF load into Virtuoso (per graph) and indexing into Elasticsearch.
vocabularies.dbaggregation — the aggregated SQLite DB of vocabularies is regenerated at the end of every run (ETag persisted in the.aggregate-hashsidecar).- Outcome persistence on MySQL (
HARVESTER_RUN) andValidationReportgeneration.
Repositories are registered in MySQL and managed via /config/repository. Manual triggering is exposed at POST /jobs/harvest (Basic Auth required, plus HARVESTER_ENDPOINT_ENABLED=true).
The container image is produced with Jib (no Dockerfile required):
just docker-build
# or: ./gradlew jibDockerBuildThe base image is gcr.io/distroless/java17-debian11:nonroot. The service exposes ports 8080 (HTTP) and 8081 (management actuator).
For a fully containerized stack (backend + dependencies) the backend section in docker-compose.yaml is commented out and can be enabled once the image is built; the main environment variables are documented inline.
Production deployment is managed via Kubernetes (see teamdigitale/dati-semantic-kubernetes).
- Checkstyle — config in
config/checkstyle/, runs onmain,test,integration(just lint). - SpotBugs — static analysis; runs alongside Checkstyle.
- JaCoCo — coverage with threshold verification (
just coverage). - OWASP dependency-check — dependency vulnerability scanning (
just dep-check). - GitHub Dependabot — enabled at the
teamdigitaleorganization level. - Pre-commit hook — Gradle checkstyle + spotbugs are invoked automatically; see
config/hooks/pre-commit.
Analysis reports are written under build/reports/.
Before opening a PR:
- Read the Code of Conduct and the CONTRIBUTING guide.
- Create a branch from
mainwith afeat-*,fix-*, ordocs-*prefix. - Run
just checklocally (lint + tests). - Open the PR against
main. CI checks are mandatory.
For discussions and support:
- Official wiki: https://github.com/teamdigitale/dati-semantic-backend/wiki
This project is released under the GNU Affero General Public License v3.0 or later. A copy of the license is available in LICENSE.