Skip to content
Open
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
13 changes: 9 additions & 4 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -497,10 +497,15 @@ Dependencies are resolved from the `ContextResolver` based on type matching.

### GraalVM Support

Templates must declare reflective classes for native image compilation:
- Override `getReflectiveClasses()` in template
- Pass additional classes to `AbstractChangeTemplate` constructor
- `RegistrationFeature` in `flamingock-graalvm` module handles registration
The `RegistrationFeature` in `flamingock-graalvm` registers templates for native-image reflection without instantiating them at build time (instantiation would fire each template's `<clinit>` and pull SLF4J/Logback into the build heap).

Per-template reflection metadata comes from class-readable sources only:
- Generic type parameters of `AbstractChangeTemplate<CONFIG, APPLY, ROLLBACK>` — auto-registered.
- `@ChangeTemplate(reflectiveClasses = {...})` — for any extra classes the template's apply/rollback methods touch reflectively that aren't in the generic type signature.

Template authors writing their own templates should:
- Annotate with `@ChangeTemplate(name = "...", reflectiveClasses = {...})` — the `reflectiveClasses` element is optional and defaults to empty.
- Avoid heavy work in `<clinit>`. In particular, the conventional `private static final Logger log = LoggerFactory.getLogger(...)` pattern is fine for runtime but can be defensive against future native-image changes; consider a lazy holder pattern if the template ships in a library that other projects native-compile.

### Evolution Proposals

Expand Down
8 changes: 4 additions & 4 deletions build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -19,10 +19,10 @@ allprojects {
version = "1.3.0-SNAPSHOT"

extra["generalUtilVersion"] = "1.5.3"
extra["templateApiVersion"] = "1.3.3"
extra["coreApiVersion"] = "1.3.2"
extra["sqlVersion"] = "1.3.1"
extra["mongodbTemplateVersion"] = "1.3.1"
extra["templateApiVersion"] = "1.3.4-SNAPSHOT"
extra["coreApiVersion"] = "1.3.3-SNAPSHOT"
extra["sqlVersion"] = "1.3.2-SNAPSHOT"
extra["mongodbTemplateVersion"] = "1.3.2-SNAPSHOT"

repositories {
mavenLocal()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,11 @@

public final class Constants {

public static final String FULL_PIPELINE_FILE_PATH = "META-INF/flamingock/metadata.json";

public static final String FULL_GRAALVM_REFLECT_CLASSES_PATH = "META-INF/flamingock/reflection-classes.txt";
// Phase 2: per-module metadata file paths are now dynamic (suffixed by an 8-hex
// identifier shared with the generated FlamingockMetadataProvider class). The legacy
// single-file constants `FULL_PIPELINE_FILE_PATH` / `FULL_GRAALVM_REFLECT_CLASSES_PATH`
// were removed. Resource discovery now goes through ServiceLoader on
// FlamingockMetadataProvider — see MetadataLoader for runtime usage.

public static final String DEFAULT_MONGOCK_ORIGIN = "mongockChangeLog";

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,10 @@
*/
package io.flamingock.internal.common.core.metadata;

import io.flamingock.internal.common.core.preview.CodePreviewChange;
import io.flamingock.internal.common.core.preview.PreviewPipeline;

import java.util.List;
import java.util.Map;

public class FlamingockMetadata {
Expand All @@ -25,6 +27,8 @@ public class FlamingockMetadata {
private String configFile;
private Map<String, String> properties;
private BuilderProviderInfo builderProvider;
private List<CodePreviewChange> orphanChanges;
private boolean strictStageMapping;

public FlamingockMetadata() {
}
Expand Down Expand Up @@ -75,11 +79,40 @@ public boolean hasValidBuilderProvider() {
return builderProvider != null && builderProvider.isValid();
}

/**
* Code-changes that have not yet been placed into any stage. Populated when an incremental
* compilation round discovers a {@code @Change} whose package isn't covered by any stage in
* the cached pipeline. They are rehomed by the merger when {@code @EnableFlamingock} is
* processed in a subsequent round and a stage now covers their package.
*/
public List<CodePreviewChange> getOrphanChanges() {
return orphanChanges;
}

public void setOrphanChanges(List<CodePreviewChange> orphanChanges) {
this.orphanChanges = orphanChanges;
}

/**
* Whether {@code @EnableFlamingock.strictStageMapping} was true when the metadata was last
* generated. Persisted so the runtime can fail when orphans remain.
*/
public boolean isStrictStageMapping() {
return strictStageMapping;
}

public void setStrictStageMapping(boolean strictStageMapping) {
this.strictStageMapping = strictStageMapping;
}

@Override
public String toString() {
int orphans = orphanChanges == null ? 0 : orphanChanges.size();
return "FlamingockMetadata{" + "pipeline=" + pipeline +
", configFile='" + configFile + '\'' +
", builderProvider=" + builderProvider +
", orphanChanges=" + orphans +
", strictStageMapping=" + strictStageMapping +
'}';
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
/*
* Copyright 2026 Flamingock (https://www.flamingock.io)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.flamingock.internal.common.core.metadata;

/**
* Service Provider Interface registered per Flamingock-aware module.
*
* <p>Each module's annotation processor generates an implementation of this interface and
* declares it in {@code META-INF/services/io.flamingock.internal.common.core.metadata
* .FlamingockMetadataProvider}. At runtime the {@code MetadataLoader} discovers all providers
* via {@link java.util.ServiceLoader} and aggregates each module's metadata file into a single
* composite {@link FlamingockMetadata} consumed by the runner.
*
* <p>Providers are stateless: they only advertise the resource path of the metadata file they
* own. The path is module-unique (suffixed at compile time) so multiple modules can coexist on
* the same classpath without colliding.
*/
public interface FlamingockMetadataProvider {

/**
* @return the classpath resource path where this module's serialized
* {@link FlamingockMetadata} JSON file lives, e.g.
* {@code "META-INF/flamingock/metadata_a1b2c3d4.json"}. Loaders use this with
* {@link ClassLoader#getResourceAsStream(String)}.
*/
String getMetadataResourcePath();

/**
* @return the classpath resource path where this module's GraalVM reflection-classes
* file lives. Default implementation derives the path from
* {@link #getMetadataResourcePath()} by replacing the {@code metadata_} segment
* with {@code reflection-classes_} and the {@code .json} extension with
* {@code .txt}. Generated providers may override if their layout differs.
*/
default String getReflectClassesResourcePath() {
return getMetadataResourcePath()
.replace("/metadata_", "/reflection-classes_")
.replace(".json", ".txt");
}
}
Loading
Loading