This file provides guidance to AI agents when working with code in this repository.
Uttu is the back-end for Nplan, a timetable editor for flexible transport services. It provides GraphQL APIs for managing lines (both fixed and flexible), journey patterns, service journeys, day types, and exports to NeTEx format. The front-end is Enki.
Technology Stack: Spring Boot 3.x, Java 21, PostgreSQL with PostGIS, JPA/Hibernate, GraphQL Java, Jersey (JAX-RS), NeTEx XML model
./mvnw clean install # Full build with tests
./mvnw clean package # Build without installing to local repo
./mvnw prettier:write # Format code (runs automatically on validate phase)Uttu uses Prettier Java for code formatting. The prettier plugin runs automatically during the validate phase, but you can run it manually:
./mvnw prettier:write # Format all Java files
./mvnw -PprettierSkip install # Skip prettier formattingConfiguration: 90 character line width, 2-space indentation, no tabs.
./mvnw test # Run all tests
./mvnw test -Dtest=ClassName # Run specific test class
./mvnw test -Dtest=ClassName#methodName # Run specific test methodTests use Testcontainers for PostgreSQL, Google Cloud Pub/Sub emulator, and LocalStack (AWS).
Prerequisites: Docker Compose must be running for local development.
- Start dependencies:
docker compose up -d- Initialize database:
src/main/resources/db_init.sh- Run application:
# Using Maven
./mvnw spring-boot:run
# Full quickstart with all settings
./mvnw spring-boot:run -Dspring-boot.run.profiles=local,local-disk-blobstore,local-no-authentication \
-Dspring-boot.run.jvmArguments='
-Duttu.organisations.netex-file-uri=src/test/resources/fixtures/organisations.xml
-Duttu.stopplace.netex-file-uri=src/test/resources/fixtures/stopplaces.xml
-Dblobstore.gcs.container.name=foobar
-Duttu.security.user-context-service=full-access
-Dspring.cloud.aws.s3.enabled=false
-Dspring.cloud.gcp.pubsub.enabled=false
-Dspring.cloud.aws.secretsmanager.enabled=false'Application runs on http://localhost:11701 by default.
- Provider-independent:
/services/flexible-lines/providers/graphql - Provider-specific:
/services/flexible-lines/{providerCode}/graphql - Export download:
/services/flexible-lines/{providerCode}/export/
Key packages under no.entur.uttu:
- config - Configuration (Context for ThreadLocal multi-tenancy, timezone, geometry factory)
- error - Error handling (coded errors/exceptions, error codes enumeration)
- export - NeTEx export system (blob storage, messaging, netex generation, export service)
- graphql - GraphQL API layer (schema definitions, fetchers, mappers, resources, scalars)
- model - Domain entities (JPA entities, all in
no.entur.uttu.model) - repository - JPA repositories with custom base class for provider filtering
- organisation - Organisation registry integration (authorities, operators)
- stopplace - Stop place registry integration
- security - Security configuration and user context services
- service - Business logic services
- routing - OSRM routing integration for service links
Uttu implements multi-tenancy through a Provider system. Each provider has:
- A unique code (used in URLs and as tenant identifier)
- A codespace (determines NeTEx namespace for XML exports)
- Isolated data through provider-scoped entities
The Context class (using ThreadLocal) stores the current provider code and username for each request. All entities extending ProviderEntity are automatically scoped to a provider.
Key Classes:
no.entur.uttu.config.Context- ThreadLocal provider/user contextno.entur.uttu.model.Provider- Provider entityno.entur.uttu.model.ProviderEntity- Base class for all provider-scoped entitiesno.entur.uttu.repository.generic.ProviderEntityRepositoryImpl- Custom JPA repository base class that automatically filters by provider
GraphQL is the primary API interface. The schema is programmatically defined (not schema-first):
Schema Definition:
LinesGraphQLSchema.java- Defines the complete GraphQL schema for flexible/fixed lines using GraphQL Java buildersProviderGraphQLSchema.java- Defines provider-level schemaGraphQLNames.java- Constants for all field names to ensure consistency
Data Layer:
- Fetchers in
no.entur.uttu.graphql.fetchers.*implement query/mutation logicAbstractProviderEntityUpdater<T>- Base class for mutations, handles both save and delete (distinguished by field name starting with "delete")
- Mappers in
no.entur.uttu.graphql.mappers.*convert between GraphQL inputs and JPA entitiesAbstractProviderEntityMapper<T>- Base class for entity mapping, usesArgumentWrapperfor field extraction- For new entities, creates instance and sets provider from Context. For updates, fetches existing by netexId
Request Flow:
- JAX-RS resource (
LinesGraphQLResource) receives request at/{providerCode}/graphql - Security check via
@PreAuthorizevalidates user access to provider Context.setProvider(providerCode)sets ThreadLocal for tenant isolationGraphQLResourceHelperwraps execution in Spring transaction- DataFetcher called → Mapper converts input → Repository saves (auto-filtered by provider)
- Transaction commits, response returned
Important: When adding new fields or types to the GraphQL API, you must update the Java schema definition in LinesGraphQLSchema.java by creating new GraphQLObjectType and GraphQLInputObjectType definitions.
Core entities (all in no.entur.uttu.model):
- Line (abstract) → FixedLine and FlexibleLine - Transport line definitions
- JourneyPattern - Ordered sequence of stop points for a line
- ServiceJourney - Specific journey on a pattern with passing times
- StopPointInJourneyPattern - Stop point in a journey pattern (references either fixed stops via quayRef or FlexibleStopPlace)
- FlexibleStopPlace - Flexible service area (polygon-based or hail-and-ride)
- DayType - Defines which days services operate (days of week + date assignments)
- Network - Groups lines by transport authority
- Export - Represents a NeTEx export job with status tracking
All provider-scoped entities extend ProviderEntity, which:
- Auto-generates NeTEx IDs in format
{codespace}:{EntityType}:{UUID} - Enforces provider isolation via
@PreUpdateverification - Maintains version tracking for optimistic locking
Exports generate NeTEx XML files conforming to the Nordic NeTEx Profile for flexible transport.
Flow:
- GraphQL mutation triggers
ExportUpdater - Export entity created with status
IN_PROGRESS - Background job processes export via
ExportService.exportDataSet():- Queries all valid lines for provider and date range (optionally filtered by
exportLineAssociations) NetexLineFileProducergenerates NeTEx XML for each lineNetexCommonFileProducergenerates shared data (authorities, operators, networks, day types, stop places)- Marshals to XML using JAXB
- Optional NeTEx schema validation
DataSetProducerpackages files into ZIP
- Queries all valid lines for provider and date range (optionally filtered by
- Uploads to BlobStore (production + backup files)
- Optional notification via
MessagingService.notifyExport()(e.g., to Marduk) - Status updated to
SUCCESSorFAILEDwith validation messages
BlobStore profiles (choose one):
in-memory-blobstore- For testinglocal-disk-blobstore- Local filesystem storagegcp-blobstore- Google Cloud Storages3-blobstore- Amazon S3
Export can optionally generate ServiceLink geometries using OSRM routing if generateServiceLinks=true.
Custom repository base class ProviderEntityRepositoryImpl automatically:
- Sets provider from Context on entity creation
- Filters all queries by current provider (tenant isolation)
- Handles provider verification on updates
When creating new repositories for provider-scoped entities, extend ProviderEntityRepository<T>.
Security is pluggable via profiles:
- OAuth2/JWT validation via
uttu.security.jwt.issuer-uri - User context service determines permissions (
full-accessgives all permissions) local-no-authenticationprofile disables auth for development- Provider access controlled via UserContextService implementations
Organisation Registry: Required for populating authority/operator references
- Configure via
uttu.organisations.netex-file-uri(file) oruttu.organisations.netex-http-uri(HTTP) - Can override organisation IDs via
no.entur.uttu.organisations.overridesproperty - See
no.entur.uttu.organisation.spi.OrganisationRegistry
Stop Place Registry: Required for looking up fixed stop places (quays)
- Configure via
uttu.stopplace.netex-file-uri - See
no.entur.uttu.stopplace.spi.StopPlaceRegistry
Messaging Service: Optional notification on export completion
- Implement
no.entur.uttu.export.messaging.spi.MessagingService - Default implementation is no-op
Always ensure Context is properly set for provider-scoped operations. The GraphQL resources handle this automatically, but when adding new endpoints or background jobs, use:
Context.setProvider(providerCode);
try {
// Your provider-scoped operations
} finally {
Context.clear();
}- Entities extending
ProviderEntityget auto-generated NeTEx IDs on persist (format:{codespace}:{EntityType}:{UUID}) @PrePersistsets created timestamp and user fromContext.getVerifiedUsername()@PreUpdateverifies provider matches context and sets changed timestamp/user- Provider verification happens automatically on update via
ProviderEntity.verifyProvider() - Use optimistic locking (
@Version Long version) to prevent concurrent modification conflicts
Errors may include extension codes from ErrorCodeEnumeration. Add new error codes there when creating validation logic.
Use Flyway migrations in src/main/resources/db/migration/. Name format: V{number}__{description}.sql
- Use
@SpringBootTestwith testcontainers for integration tests - Test fixtures in
src/test/resources/fixtures/ - GraphQL tests should use
spring-graphql-testframework
- Update the
GraphQLObjectTypedefinition inLinesGraphQLSchema.javausingnewFieldDefinition() - Add corresponding field to the entity class
- Update mapper's
populateEntityFromInput()method if needed (useArgumentWrapperfor extraction) - Create Flyway migration if adding to persistence:
src/main/resources/db/migration/V{number}__{description}.sql - Format code with
./mvnw prettier:write
- Create entity class extending
ProviderEntity(for provider-scoped) orIdentifiedEntity - Create repository interface extending
ProviderEntityRepository<T>(auto-gets provider filtering) - Create GraphQL types in
LinesGraphQLSchema.java:GraphQLObjectTypefor output type usingnewObject()GraphQLInputObjectTypefor mutation input usingnewInputObject()
- Create mapper extending
AbstractProviderEntityMapper<T>ingraphql.mapperspackage- Implement
createNewEntity()andpopulateEntityFromInput()
- Implement
- Create updater extending
AbstractProviderEntityUpdater<T>ingraphql.fetcherspackage- Wire mapper + repository to parent constructor
- Add query/mutation fields to schema's query/mutation objects with appropriate data fetchers
- Create Flyway database migration
Available Spring profiles:
local- Local development (seeapplication-local.properties)local-no-authentication- Disable authenticationlocal-disk-blobstore- Store exports on local diskin-memory-blobstore- In-memory export storagegcp-blobstore- Google Cloud Storages3-blobstore- AWS S3 storage
Combine profiles with comma separation: -Dspring-boot.run.profiles=local,local-no-authentication
Configure additional NeTEx codespaces for exports:
no.entur.uttu.codespaces.additional.nsr=http://www.rutebanken.org/ns/nsr
no.entur.uttu.codespaces.additional.nog=http://www.rutebanken.org/ns/nog