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
28 changes: 8 additions & 20 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -169,26 +169,14 @@ JS/TS user code is **not** synchronized — it is loaded on demand by `engine-ja

## Client Java code (`engine-java` + `data-store-java`)

Client `.java` sources dropped under `/registry/public/<project>/...` ARE synchronized — by `JavaSynchronizer` (`components/engine/engine-java`, artifact `dirigible-components-engine-java`). Knowing the moving parts saves a lot of grep time:

- **One compiler, one classloader, one batch per cycle.** `JavaSynchronizer.parseImpl` only parses + persists the `JavaFile` artefact (and enforces global FQN uniqueness). The `completeImpl` calls just flip a dirty flag. The expensive work — single `javac` task over every client source, single fresh `ClientClassLoader` install, consumer fan-out — happens in `finishing()`. Cross-file references resolve in user code because every client class shares the one `ClientClassLoader` (parent = platform CL). The previous generation's CL becomes unreachable on swap and GC reclaims its Metaspace.
- **SPI: `org.eclipse.dirigible.engine.java.spi.JavaClassConsumer`.** Every loaded class is offered to every registered consumer. Four live in the codebase today, ordered via Spring `@Order` so cross-consumer dependencies resolve within one rebuild cycle:
- `EntityClassConsumer` (data-store-java, `@Order(100)`) claims `@Entity` classes → registers them with `JavaEntityManager` (table created first).
- `RepositoryClassConsumer` (data-store-java, `@Order(200)`) claims `@Repository` classes → instantiates each via public no-arg ctor, stores singleton in `RepositoryRegistry`.
- `ControllerClassConsumer` (engine-java, `@Order(300)`) claims classes annotated with `@Controller` → resolves `@Inject` fields via the `DependencyResolver` chain (`RepositoryRegistry` is the only resolver today), registers routes with `ControllerRouter`, emits OpenAPI fragment via `JavaControllerOpenApiPublisher`.
- `HandlerClassConsumer` (engine-java, unordered → LOWEST_PRECEDENCE) claims `implements JavaHandler` → publishes to `JavaClassRegistry`.

`JavaLoader.rebuild()` iterates **consumer-outer / class-inner** (each consumer drains its claimed classes before the next consumer runs). With the `@Order` chain above this guarantees `@Inject CountryRepository` resolves inside `ControllerClassConsumer` because `RepositoryClassConsumer` has already registered every `@Repository` for that rebuild generation. A class may be exactly one of {handler, controller}; carrying both shapes is rejected with an error log. Future "react to compiled client classes" features should plug into this SPI rather than introduce a second synchronizer.
- **Two annotation packages, both in `engine-java`** (engine-java sits on the compile-time classpath of every client `.java`):
- `org.eclipse.dirigible.engine.java.annotations.*` — entity surface mirroring JPA signatures: `@Entity`, `@Table`, `@Id`, `@GeneratedValue` (+ `GenerationType`), `@Column`, `@Transient`, `@CreatedAt`/`@UpdatedAt`/`@CreatedBy`/`@UpdatedBy`, `@Documentation` (now also valid on methods for OpenAPI summaries).
- `org.eclipse.dirigible.engine.java.annotations.http.*` — REST surface: `@Controller` (class marker), `@Get`/`@Post`/`@Put`/`@Patch`/`@Delete` (method-level with `value()` path suffix), `@Body`/`@PathParam`/`@QueryParam`/`@Context` (parameter binding), `@Roles` (class- or method-level, any-of role check).
- **`JavaEndpoint` dispatches controllers first.** The single URL pattern `/services/java/{project}/{*classPath}` (+ `/public/java/...`) funnels into `dispatch(httpMethod, project, classPath, req, resp)` which (1) tries `ControllerRouter.match` and invokes via `ControllerInvoker` if it hits; (2) falls through to `JavaClassRegistry.find` + `JavaHandler.handle`; (3) returns 404 otherwise. The handler path keeps the TCCL swap; controllers reuse their long-lived instance.
- **Controller routing is Spring-style.** Base path is the class FQN with slashes (`demo/CountryController`); each `@Get("/list")`/`@Get("/{id}")`/etc. annotation supplies a suffix. `ControllerRouter` picks the longest matching basePath (so nested-package controllers win over their outer namespaces) and then the most specific route within (literal paths beat `{placeholder}` patterns via `PathPattern.specificity`). Path placeholders compile to named regex groups; `TypeCoercer` handles String/int/long/UUID/enum/boolean conversion at bind time and surfaces parse failures as `400`. `@Body` deserializes via Spring's primary `ObjectMapper`. Return values: `void` → write-it-yourself, `String`/`CharSequence` → `text/plain`, everything else → Jackson JSON.
- **`@Roles` mirrors `UserFacade.isInRole` semantics** without pulling `api-security` (which transitively brings `engine-javascript`). `ControllerInvoker.checkRoles` short-circuits on `Configuration.isAnonymousModeEnabled()`/`isAnonymousUserEnabled()`, then on the `DEVELOPER`/`ADMINISTRATOR` super-roles, then iterates the declared roles via `HttpServletRequest.isUserInRole`. Method-level `@Roles` overrides class-level for that method only.
- **Auto-generated OpenAPI.** `JavaControllerOpenApiPublisher` reflects each controller class into a minimal OpenAPI 3 JSON fragment and saves it as an `OpenAPI` artefact at location `java-controller://<project>::<fqn>` via `OpenAPIService`. The existing aggregator at `OpenAPIEndpoint.getVersion()` (`/services/openapi`) merges every stored artefact — Java-controller fragments appear alongside TS-controller fragments without further wiring. Body/return-type schemas are conservative (`object`/`array`/scalar); the hook is in place to enrich later.
- **`data-store-java` runs Hibernate in dynamic-map mode** — `session.save(entityName, Map<String, Object>)` rather than `session.save(typedBean)`. Hibernate never has to load the user's `Class<?>`, which sidesteps the cross-classloader gymnastics. `JavaEntityStore` provides a typed CRUD API to clients; bean ↔ Map conversion (with column-name / `@Transient` handling and JDBC-type coercion) lives in `EntityBeanMapper`. The SessionFactory is rooted at `DataSourcesManager.getDefaultDataSource()` (the user-data DB), not SystemDB.
- **HBM XML serializer is reused** from `data-store`: `JavaEntityToHbmMapper` (in `data-store-java`) reflects over annotations and feeds `HbmXmlDescriptor` / `HbmPropertyDescriptor` from `data-store`. If you change the HBM serializer for one, audit both.
- **Reaching platform beans from client code.** Client classes are loaded via `ClientClassLoader`, not Spring-scanned, so `@Autowired` is a no-op on them. Use `BeanProvider.getBean(...)` (from `components-core-base`) inside controller / handler methods to fetch `JavaEntityStore`, `IRepository`, etc. The recommended client-code pattern is `@Inject CountryRepository` — see [`dirigiblelabs/sample-java-entity-decorators`](https://github.com/dirigiblelabs/sample-java-entity-decorators); `JavaEntityDecoratorsSampleProjectIT` clones and exercises that repo end-to-end.
Client `.java` under `/registry/public/<project>/...` is synchronized by `JavaSynchronizer`, compiled in-process (one `javac` batch + one fresh `ClientClassLoader` per generation in `JavaLoader.rebuild()`), and run through a Spring-Boot-style **bean container** (PR [#6051](https://github.com/eclipse-dirigible/dirigible/pull/6051)):

- `@Component` beans with **constructor / field / collection** injection; `@Repository`, `@Controller`, `@Websocket` are meta-`@Component`. Reach platform services via the client-facing `Beans` facade (not the platform-internal `BeanProvider`).
- **Two never-mixed handler styles** for jobs/listeners/websockets: a self-describing interface (`JobHandler.cron()`, `MessageHandler.destination()`, `WebsocketHandler.endpoint()`) **or** a method-level annotation (`@Scheduled`/`@Listener` on a `@Component` method; `@Websocket` class + `@OnX` methods). No reflective by-name fallback; the hybrid is rejected.
- **Extension points are plain interfaces** + `@Component` contributions consumed via `List<…>` injection (or `Extensions.find`); there is no `@Extension`/`@ExtensionPoint`.
- All client annotations/facades live in `org.eclipse.dirigible.sdk.*` (`api-modules-java`), not the old `engine.java.annotations.*`. Compile **and** bean-wiring errors surface in the IDE Problems view.

**Detailed guide:** [`components/engine/engine-java/CLAUDE.md`](components/engine/engine-java/CLAUDE.md). Read it before changing anything under `engine-java`, `data-store-java`, the `sdk.*` annotations, or the `*-java` templates — it covers the container, the consumers, the two handler styles + no-mixing rule, the `JavaHandler`-as-bean path, controller routing / OpenAPI / `@Roles`, `data-store-java` dynamic-map persistence, error surfacing, the **removed** internals (`RepositoryRegistry` / `RepositoryClassConsumer` / `DependencyResolver` / reflective fallback / `@Extension`), and the three-repo (platform + `dirigiblelabs/sample-java-*` + docs) sequencing.

## Intent layer (`engine-intent` + `editor-intent`)

Expand Down
Loading
Loading