Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 18 additions & 0 deletions .bito.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
# Generated by seed-golden-context | Last updated: 2026-05-11
suggestion_mode: comprehensive
post_description: true
post_changelist: true
exclude_files: ''
exclude_draft_pr: false
secret_scanner_feedback: true
linters_feedback: true
repo_level_guidelines_enabled: true
sequence_diagram_enabled: true
custom_guidelines:
general:
- name: 'Review Posture'
path: './.bito/guidelines/review-posture.txt'
- name: 'Repo Truth And Alignment'
path: './.bito/guidelines/repo-truth-and-boundaries.txt'
- name: 'Domain Invariants'
path: './.bito/guidelines/domain-invariants.txt'
49 changes: 49 additions & 0 deletions .bito/guidelines/domain-invariants.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
Critical invariants for contentful.java. Violations are bugs, not style issues.

POST-PROCESSING PIPELINE ORDER (ResourceFactory.array()):
1. localizeResources() — apply locale fallback chain
2. mapResources() — index assets and entries by ID
3. setRawFields() — capture raw JSON field map before any mutation
4. resolveRichTextField() — parse rich text JSON into CDARich* node tree
5. resolveLinks() — replace link stubs with resolved CDAResource references
Never reorder these steps without understanding the full cascade of side effects.

LINK RESOLUTION:
- Unresolved links are silent placeholder objects, NOT exceptions. Consumer code must handle null/stub gracefully.
- Include depth max is 10 (API limit). Links beyond that depth remain unresolved.
- Cross-space resolution only resolves the first level of cross-space references.

PUBLIC API CONTRACT:
- CDAClient, CDAClient.Builder, CDAEntry, CDAAsset, CDAArray, SynchronizedSpace, and all CDAResource subclasses are public API.
- Adding new builder methods is non-breaking. Removing or renaming them is a breaking change requiring a major version bump.
- The sys.* fields are always returned, regardless of select() usage.

ANDROID COMPATIBILITY:
- Source/target must remain Java 1.8. Do not use APIs requiring Java 9+.
- Do not introduce Android API calls above API 21 in main source.
- TlsSocketFactory only activates below Android API 20 — do not change this guard.
- logSensitiveData MUST default to false — auth tokens must never be logged by default.

OKHTTP / ANDROID:
- okhttp-jvm is declared as a dependency for JVM consumers.
- Android consumers must exclude okhttp-jvm and depend on okhttp-android — this is a known setup step (documented in README).

SENSITIVE DATA:
- logSensitiveData defaults to false. Never flip in production code. AuthorizationHeaderInterceptor must redact the token by default.

TRANSFORMQUERY:
- observeAndTransform() auto-injects a select filter — only annotated fields are fetched.
- Rich text fields are NOT accessible via TransformQuery — consumers must use rawFields or direct HTTP.
- Only works with CDA, not CMA.

SYNC:
- Sync tokens are environment-specific. Passing a SynchronizedSpace from environment A to a client pointed at environment B produces incorrect deltas.
- Deleted resources appear ONLY in deletedEntries()/deletedAssets() — they do not appear in the items() list.

CROSS-SPACE:
- Maximum 20 additional spaces (21 total). Exceeding this limit requires a different architecture.
- Cross-space errors surface via CDAArray.getErrors() — they do not throw.

RELEASE:
- GPG signing is required for Maven Central publication. Do not attempt release without the Contentful GPG key.
- master is the trunk branch. There is no main branch.
9 changes: 9 additions & 0 deletions .bito/guidelines/repo-truth-and-boundaries.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
Use the repository's written documentation as review context and check whether the change matches documented intent.

- Start from README.md, ARCHITECTURE.md, AGENTS.md, CONTRIBUTING.md, and docs/ADRs/ for architectural context.
- Check whether code, tests, and documentation all tell the same story. Flag mismatches between implementation and documented architecture or ADRs.
- Treat AGENTS.md as the authoritative guide for sharp edges and invariants. If a change violates an invariant documented there, flag it.
- Public API changes (CDAClient builder methods, CDAResource subclasses, query builder methods) require extra scrutiny — this is a published library used by external consumers.
- Architecture-significant changes (new dependency, new module, new HTTP interceptor, new deserialization path) should be accompanied by an ADR update or new ADR in docs/ADRs/.
- If CI or another required check already enforces a merge rule, do not ask for duplicate PR sections or manual checklists.
- The SDK targets Java 8 (source/target 1.8) and Android API 21 minimum — flag any change that requires a higher Java or Android API level.
9 changes: 9 additions & 0 deletions .bito/guidelines/review-posture.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
Review this pull request like the tech lead of the contentful.java project — the official Java/Android SDK for Contentful's Content Delivery API.

- Prefer a few high-signal findings to a long list of minor or style-only comments.
- Prioritize: public API contract changes, behavioral correctness in link resolution and deserialization, Android compatibility, backwards compatibility, and security (auth header handling, sensitive data logging).
- Flag any change to the post-processing pipeline in ResourceFactory (localization → raw fields → rich text → link resolution) — order matters and regressions are silent.
- Keep feedback actionable: explain why it matters, how it would surface in practice, and the clearest next step.
- If a concern is only a risk or assumption rather than a confirmed bug, say that clearly.
- If you find no issues, say so explicitly and call out any residual uncertainty that still deserves human attention.
- Do not ask for duplicate PR template sections or manual checklists when CI already enforces them.
62 changes: 62 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
<!-- Generated by seed-golden-context | Last updated: 2026-05-11 -->

# Agent Guide

Read this file first. It tells you where to find context in this repo.

## Quick Reference

| What you need | Where to look |
|---|---|
| How this repo is structured | [ARCHITECTURE.md](./ARCHITECTURE.md) |
| How to build/test/run | [CONTRIBUTING.md](./CONTRIBUTING.md) |
| Why decisions were made | [docs/ADRs/](./docs/ADRs/) |
| What this repo does | [README.md](./README.md) |
| PR review rules | [.bito/guidelines/](./.bito/guidelines/) |
| Active specs/work | [docs/specs/](./docs/specs/) |

## Sharp Edges & Invariants

- **Link resolution order matters** — `ResourceUtils.resolveLinks()` must run after localization and raw-field capture. Never reorder the post-processing pipeline in `ResourceFactory.array()` without understanding all downstream side effects.
- **Unresolved links are silent, not exceptions** — if an included entry's depth exceeds the API limit (10), unresolved links remain as placeholder objects. Code that dereferences resolved links must handle null/stub gracefully.
- **`sys` fields are always present** — the SDK enforces that `sys.*` attributes are always returned. When using `.select()` with `fields.*` selections, `.withContentType()` must be called first or the SDK throws a client-side `IllegalStateException` before the request is sent. Selecting only `sys` fields does not require a content type.
- **`rawFields` is the only path to raw rich text JSON** — `TransformQuery` (unwrapping) does not expose raw rich text; use `CDAEntry.rawFields` or make a direct HTTP request.
- **Cross-space token limit is 20** — `setCrossSpaceTokens()` accepts at most 20 extra spaces (21 total). Only the first level of cross-space references is resolved.
- **Sync tokens are stateful and environment-aware** — passing a `SynchronizedSpace` from the wrong environment to `client.sync()` will produce incorrect deltas.
- **Android: never depend on `okhttp-jvm` directly when using `okhttp-android`** — exclude `okhttp-jvm` from this library to avoid duplicate-class errors on Android (see README and ADR 0002).
- **`TlsSocketFactory` only activates below Android API 20** — do not remove or gate it differently; it protects older devices.
- **`TransformQuery.observeAndTransform()` auto-injects a `select` filter** — if the annotation scanner has a bug, fields are silently dropped, not thrown as errors. Always validate field mapping in tests.
- **`logSensitiveData` defaults to `true`** — always set this to `false` in production builds to avoid logging auth tokens.
- **Release requires GPG signing** — the Maven Central publication flow requires the Contentful GPG key imported locally. Do not attempt to release without it.
- **`master` is the trunk** — there is no `main` branch. All CI and release flows target `master`.

## Key Conventions

- **Commit format**: Conventional Commits preferred (`feat:`, `fix:`, `chore:`, `docs:`, etc.) — see recent history for examples
- **Branch strategy**: Trunk-based development off `master`; feature branches merged via PR with squash or merge commit
- **Test location**: `src/test/java/…/cda/` mirroring the main package; fixtures in `src/test/resources/`
- **Java version**: Java 8 source/target (`java.version=1.8` in `pom.xml`); devcontainer uses `eclipse-temurin:8-jdk-jammy` by default
- **Build tool**: Maven Wrapper (`./mvnw`) — do not require a globally installed Maven

## Integration Points

**Upstream (this repo consumes):**
- Contentful Content Delivery API — `https://cdn.contentful.com`
- Contentful Content Preview API — `https://preview.contentful.com`

**Downstream (consumes this repo):**
- Java/Kotlin server and Android applications via Maven Central (`com.contentful.java:java-sdk`)
- `rich-text-renderer-java` companion library (consumes the `CDARich*` node model)

## Build & Quality

```bash
# Spin up the devcontainer (first time or after Docker changes)
devcontainer up --workspace-folder .

# Full verification loop inside the container
devcontainer exec --workspace-folder . bash -lc "./mvnw -B test"

# Checkstyle only
devcontainer exec --workspace-folder . bash -lc "./mvnw -B checkstyle:checkstyle"
```
122 changes: 122 additions & 0 deletions ARCHITECTURE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
<!-- Generated by seed-golden-context | Last updated: 2026-05-11 -->

# Architecture

## Overview

`contentful.java` is the official Java SDK for Contentful's Content Delivery API (CDA) and Content Preview API (CPA). It is a pure-Java library (ships as a JAR) that wraps the Contentful HTTP APIs in an idiomatic Java client, providing automatic link resolution, locale fallback, rich text parsing, sync, and optional reactive (RxJava 3) access patterns.

## System Context

```mermaid
graph TD
A[Java / Android Application] --> B[CDAClient]
B --> C[Contentful Content Delivery API]
B --> D[Contentful Content Preview API]
B --> E[Cross-Space CDA APIs]
C --> F[Contentful Platform]
D --> F
E --> F
```

Consumers are JVM and Android applications. The SDK wraps all outbound HTTP via OkHttp + Retrofit and returns parsed, link-resolved Java objects. No server-side component; no persistent store.

## Internal Structure

| Package / Directory | Purpose |
|---|---|
| `com.contentful.java.cda` | Root package — `CDAClient`, query builders, resource model classes, sync, serialization |
| `com.contentful.java.cda.interceptor` | OkHttp interceptors: auth header, user-agent, error translation, logging |
| `com.contentful.java.cda.rich` | Rich Text node model (`CDARichDocument`, `CDARichParagraph`, etc.) and `RichTextFactory` |
| `com.contentful.java.cda.image` | `ImageOption` helper for Images API URL construction |
| `src/main/resources/…/build` | Build-time generated `GeneratedBuildParameters.java` (injects SDK version) |
| `src/test/java/…/cda` | Unit + mock-server tests mirroring the main package |
| `src/test/java/…/cda/integration` | Integration tests against live Contentful environments |
| `src/test/resources/` | Canned JSON fixtures used by mock-server tests |
| `.devcontainer/` | Reproducible dev environment (eclipse-temurin JDK on Ubuntu Jammy) |
| `.github/workflows/` | CI pipeline (runs tests inside the devcontainer) |

## Data Flow

1. **Client construction** — `CDAClient.builder()` configures space, token, environment, optional preview mode, cross-space tokens, and HTTP client settings. Retrofit is built with a Gson converter factory and a RxJava3 call adapter factory. OkHttp interceptors inject the `Authorization` header, the `X-Contentful-User-Agent` header, and error/logging behavior.

2. **Query execution** — `CDAClient.fetch(Type.class)` returns an `AbsQuery` (or `FetchQuery`) that builds a URL and dispatches via `CDAService` (a Retrofit interface). Calls can be synchronous (`.all()`, `.one()`), callback-based (`CDACallback`), or reactive (`.observe()`/`RxJava Flowable`).

3. **Deserialization** — Gson deserializes the raw JSON response into `CDAArray`. `ResourceDeserializer` dispatches each item to the concrete `CDAResource` subclass based on the `sys.type` field.

4. **Post-processing** — `ResourceFactory.array()` orchestrates:
- `ResourceUtils.localizeResources()` — applies locale fallback chain from the space's locale list
- `ResourceUtils.mapResources()` — indexes assets and entries by ID
- `ResourceUtils.setRawFields()` — preserves the unprocessed field map for rich text access
- `RichTextFactory.resolveRichTextField()` — parses rich text JSON into the `CDARich*` node tree
- `ResourceUtils.resolveLinks()` — iterates each entry's content-type field definitions to find Link and Array-of-Link fields, then replaces link stubs with the already-indexed `CDAResource` objects from `array.assets()` / `array.entries()` (in-memory object graph). Links not present in the index are silently dropped, not thrown as errors.

5. **Result** — `CDAArray` (or single `CDAResource`) is returned to the caller with fully resolved links and localized fields.

6. **Sync path** — `CDAClient.sync()` uses `SyncQuery` and `SynchronizedSpace`. Subsequent sync calls pass the token from the prior `SynchronizedSpace` to fetch only the delta. Deleted resources appear in `deletedEntries()` / `deletedAssets()` sets.

## Key Dependencies

| Dependency | Version | Why it's here |
|---|---|---|
| `retrofit2` | 2.11.0 | HTTP client abstraction — declarative API definition via `CDAService` interface |
| `okhttp3` / `okhttp-jvm` | 5.1.0 | Underlying HTTP engine; customizable via `defaultCallFactoryBuilder()` |
| `rxjava3` | 3.1.9 | Optional reactive access pattern via `.observe()` |
| `adapter-rxjava3` | 2.11.0 | Retrofit adapter bridging Retrofit `Call` to RxJava3 `Flowable` |
| `converter-gson` | 2.11.0 | JSON deserialization via Gson |
| `gson` | 2.11.0 | Core JSON parsing; custom `ResourceDeserializer` registered for polymorphic types |
| `named-regexp` | 0.2.5 | Named capture group support used in locale/link processing |
| `android` (optional) | 4.1.1.4 | Platform detection for callback executor (main thread on Android, background thread on JVM) |
| `junit 4` | 4.13.1 | Test framework |
| `mockwebserver` | 5.1.0 | Local OkHttp mock server for unit tests |
| `mockito-core` | 2.23.4 | Mocking framework |
| `truth` | 0.42 | Fluent test assertions |

See [ADR 0002](./docs/ADRs/0002-retrofit-okhttp-as-http-layer.md) and [ADR 0003](./docs/ADRs/0003-rxjava3-reactive-access.md) for rationale on key dependency choices.

## Configuration

| Builder Method / Flag | Purpose | Default |
|---|---|---|
| `.setSpace(id)` | Contentful Space ID | Required |
| `.setToken(token)` | CDA or CPA access token | Required |
| `.setEnvironment(id)` | Environment ID | `master` |
| `.preview()` | Switches base URL to Content Preview API | `false` (CDA) |
| `.setCrossSpaceTokens(map)` | Tokens for additional spaces (max 20) | `null` (disabled) |
| `.setCallFactory(factory)` | Replace the underlying OkHttpClient | Auto-configured |
| `.defaultCallFactoryBuilder()` | Returns a pre-configured `OkHttpClient.Builder` for customization | — |
| `.setLogger(logger)` | Custom `Logger` implementation | `null` |
| `.setLogLevel(level)` | Verbosity of HTTP logs | `NONE` |
| `.logSensitiveData(bool)` | Whether to log auth header values | `false` |
| `.setCallbackExecutor(executor)` | Executor for async callbacks | Platform default (main thread on Android) |

## Integration Points

### Upstream (this repo consumes)
- **Contentful Content Delivery API** — `https://cdn.contentful.com` — primary data source
- **Contentful Content Preview API** — `https://preview.contentful.com` — preview (unpublished) content

### Downstream (consumes this repo)
- **Java / Kotlin server applications** — backend content rendering, import pipelines
- **Android applications** — content delivery on mobile (minimum Android API 21 / Java 8)
- **`rich-text-renderer-java`** — companion library that consumes the `CDARich*` node model from this SDK to render rich text

## Android Specifics

The library is distributed as a JAR (not AAR). It uses `android.os.Handler` and `android.os.Looper` (in `Platform.java`) for main-thread callback delivery on Android — both are stable public APIs. The `TlsSocketFactory` is only activated below Android API 20; on modern Android the system TLS defaults are used.

OkHttp 5 splits platform artifacts: JVM users get `okhttp-jvm` (included by default). Android apps should depend on `okhttp-android` and exclude `okhttp-jvm` to avoid duplicate-class errors. See README for Gradle snippet.

## Release & Distribution

- **Registry**: Maven Central (via Sonatype Central Publishing plugin), `groupId: com.contentful.java`, `artifactId: java-sdk`
- **Pre-release**: Sonatype snapshots and jitpack.io
- **Versioning**: Semantic Versioning — patch = bug fixes / dependency updates; minor = new non-breaking features; major = breaking API changes
- **Branching**: Trunk-based development off `master`
- **Release cadence**: On-demand
- **Build**: Maven Wrapper (`./mvnw`) — no global Maven installation required
- **Code coverage**: Codecov
- **Checkstyle**: Enforced at `verify` phase via `checkstyle.xml`
- **GPG signing**: Required for Maven Central publication (private key managed by Contentful infrastructure team)

See [CONTRIBUTING.md](./CONTRIBUTING.md) for the full release procedure.
Loading
Loading