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
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@
* @param <ROLLBACK_FIELD> rollback payload type
* @see io.flamingock.api.annotations.ChangeTemplate
*/
public abstract class AbstractChangeTemplate<SHARED_CONFIGURATION_FIELD, APPLY_FIELD, ROLLBACK_FIELD> implements ChangeTemplate<SHARED_CONFIGURATION_FIELD, APPLY_FIELD, ROLLBACK_FIELD> {
public abstract class AbstractChangeTemplate<SHARED_CONFIGURATION_FIELD, APPLY_FIELD extends TemplatePayload, ROLLBACK_FIELD extends TemplatePayload> implements ChangeTemplate<SHARED_CONFIGURATION_FIELD, APPLY_FIELD, ROLLBACK_FIELD> {

private final Class<SHARED_CONFIGURATION_FIELD> configurationClass;
private final Class<APPLY_FIELD> applyPayloadClass;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@
* @see AbstractChangeTemplate
* @see io.flamingock.api.annotations.ChangeTemplate
*/
public interface ChangeTemplate<SHARED_CONFIG_FIELD, APPLY_FIELD, ROLLBACK_FIELD> extends ReflectionMetadataProvider {
public interface ChangeTemplate<SHARED_CONFIG_FIELD, APPLY_FIELD extends TemplatePayload, ROLLBACK_FIELD extends TemplatePayload> extends ReflectionMetadataProvider {

void setChangeId(String changeId);

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
/*
* 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.api.template;

import java.util.List;

/**
* Contract for template payload types (APPLY and ROLLBACK generics).
*
* <p>All template payload types must implement this interface to enable
* structural validation at pipeline load time, before any change executes.
*/
public interface TemplatePayload {

/**
* Validates this payload and returns any errors found.
*
* @return list of validation errors, empty if payload is valid
*/
List<TemplatePayloadValidationError> validate();
}
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.api.template;

/**
* Represents a validation error found in a {@link TemplatePayload}.
*
* <p>Errors can be field-level (with a specific field name) or general (without a field).
*/
public class TemplatePayloadValidationError {

private final String field;
private final String message;

public TemplatePayloadValidationError(String message) {
this(null, message);
}

public TemplatePayloadValidationError(String field, String message) {
this.field = field;
this.message = message;
}

public String getField() {
return field;
}

public String getMessage() {
return message;
}

public String getFormattedMessage() {
return field != null ? String.format("[field: %s] %s", field, message) : message;
}

@Override
public String toString() {
return getFormattedMessage();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
/*
* 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.api.template.wrappers;

import io.flamingock.api.template.TemplatePayload;
import io.flamingock.api.template.TemplatePayloadValidationError;

import java.util.Collections;
import java.util.List;

/**
* A {@link TemplatePayload} wrapper for {@code String} payloads.
*
* <p>Provides a drop-in replacement for raw {@code String} payloads in templates,
* keeping YAML clean while satisfying the {@code TemplatePayload} contract.
*
* <p>Supports SnakeYAML deserialization via the no-arg constructor and scalar
* conversion via the {@code String} constructor.
*/
public class TemplateString implements TemplatePayload {

private String value;

/**
* No-arg constructor for SnakeYAML deserialization.
*/
public TemplateString() {
}

/**
* Constructor for scalar conversion (e.g., from YAML string values).
*
* @param value the string value
*/
public TemplateString(String value) {
this.value = value;
}

public String getValue() {
return value;
}

public void setValue(String value) {
this.value = value;
}

@Override
public List<TemplatePayloadValidationError> validate() {
if (value == null || value.trim().isEmpty()) {
return Collections.singletonList(
new TemplatePayloadValidationError("value", "must not be null or blank"));
}
return Collections.emptyList();
}

@Override
public String toString() {
return value;
}

@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
TemplateString that = (TemplateString) o;
return value != null ? value.equals(that.value) : that.value == null;
}

@Override
public int hashCode() {
return value != null ? value.hashCode() : 0;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -18,10 +18,13 @@
import io.flamingock.api.annotations.Apply;
import io.flamingock.api.annotations.ChangeTemplate;
import io.flamingock.api.annotations.Rollback;
import io.flamingock.api.template.wrappers.TemplateString;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;

import java.util.Collection;
import java.util.Collections;
import java.util.List;

import static org.junit.jupiter.api.Assertions.*;

Expand All @@ -33,13 +36,23 @@ public static class TestConfig {
}

// Simple test apply payload class
public static class TestApplyPayload {
public static class TestApplyPayload implements TemplatePayload {
public String applyData;

@Override
public List<TemplatePayloadValidationError> validate() {
return Collections.emptyList();
}
}

// Simple test rollback payload class
public static class TestRollbackPayload {
public static class TestRollbackPayload implements TemplatePayload {
public String rollbackData;

@Override
public List<TemplatePayloadValidationError> validate() {
return Collections.emptyList();
}
}

// Additional class for reflection
Expand Down Expand Up @@ -93,7 +106,7 @@ public void rollback() {
// Test template with Void configuration
@ChangeTemplate(name = "test-template-with-void-config")
public static class TestTemplateWithVoidConfig
extends AbstractChangeTemplate<Void, String, String> {
extends AbstractChangeTemplate<Void, TemplateString, TemplateString> {

public TestTemplateWithVoidConfig() {
super();
Expand Down Expand Up @@ -191,8 +204,8 @@ void getReflectiveClassesWithVoidConfigShouldIncludeVoidClass() {

assertTrue(reflectiveClasses.contains(Void.class),
"Should contain Void class for configuration");
assertTrue(reflectiveClasses.contains(String.class),
"Should contain String class for apply/rollback payloads");
assertTrue(reflectiveClasses.contains(TemplateString.class),
"Should contain TemplateString class for apply/rollback payloads");
}

@Test
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
import io.flamingock.api.annotations.ChangeTemplate;
import io.flamingock.api.annotations.Rollback;
import io.flamingock.api.template.AbstractChangeTemplate;
import io.flamingock.api.template.wrappers.TemplateString;
import io.flamingock.internal.common.core.error.FlamingockException;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
Expand All @@ -30,7 +31,7 @@
class ChangeTemplateManagerTest {

@ChangeTemplate(name = "annotated-simple-template")
public static class AnnotatedSimpleTemplate extends AbstractChangeTemplate<Void, String, String> {
public static class AnnotatedSimpleTemplate extends AbstractChangeTemplate<Void, TemplateString, TemplateString> {
public AnnotatedSimpleTemplate() {
super();
}
Expand All @@ -45,7 +46,7 @@ public void rollback() {
}

@ChangeTemplate(name = "annotated-steppable-template", multiStep = true)
public static class AnnotatedSteppableTemplate extends AbstractChangeTemplate<Void, String, String> {
public static class AnnotatedSteppableTemplate extends AbstractChangeTemplate<Void, TemplateString, TemplateString> {
public AnnotatedSteppableTemplate() {
super();
}
Expand All @@ -59,7 +60,7 @@ public void rollback() {
}
}

public static class UnannotatedTemplate extends AbstractChangeTemplate<Void, String, String> {
public static class UnannotatedTemplate extends AbstractChangeTemplate<Void, TemplateString, TemplateString> {
public UnannotatedTemplate() {
super();
}
Expand Down Expand Up @@ -113,7 +114,7 @@ void getTemplateForUnregisteredNameShouldReturnEmpty() {
}

@ChangeTemplate(name = "template-rollback-not-required", rollbackPayloadRequired = false)
public static class TemplateWithRollbackNotRequired extends AbstractChangeTemplate<Void, String, String> {
public static class TemplateWithRollbackNotRequired extends AbstractChangeTemplate<Void, TemplateString, TemplateString> {
public TemplateWithRollbackNotRequired() {
super();
}
Expand All @@ -140,7 +141,7 @@ void addTemplateWithRollbackPayloadRequiredFalseShouldPropagate() {
}

@ChangeTemplate(name = "template-without-rollback")
public static class TemplateWithoutRollback extends AbstractChangeTemplate<Void, String, String> {
public static class TemplateWithoutRollback extends AbstractChangeTemplate<Void, TemplateString, TemplateString> {
public TemplateWithoutRollback() {
super();
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
import io.flamingock.api.annotations.ChangeTemplate;
import io.flamingock.api.annotations.Rollback;
import io.flamingock.api.template.AbstractChangeTemplate;
import io.flamingock.api.template.wrappers.TemplateString;
import io.flamingock.internal.common.core.error.validation.ValidationResult;
import io.flamingock.internal.common.core.preview.TemplatePreviewChange;
import org.junit.jupiter.api.BeforeEach;
Expand All @@ -40,7 +41,7 @@ class TemplateValidatorTest {

// Test template with @ChangeTemplate (simple template)
@ChangeTemplate(name = "test-simple-template")
public static class TestSimpleTemplate extends AbstractChangeTemplate<Void, String, String> {
public static class TestSimpleTemplate extends AbstractChangeTemplate<Void, TemplateString, TemplateString> {
public TestSimpleTemplate() {
super();
}
Expand All @@ -57,7 +58,7 @@ public void rollback() {

// Test template with @ChangeTemplate(multiStep = true)
@ChangeTemplate(name = "test-steppable-template", multiStep = true)
public static class TestSteppableTemplate extends AbstractChangeTemplate<Void, String, String> {
public static class TestSteppableTemplate extends AbstractChangeTemplate<Void, TemplateString, TemplateString> {
public TestSteppableTemplate() {
super();
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
package io.flamingock.internal.core.task.executable;

import io.flamingock.api.template.ChangeTemplate;
import io.flamingock.api.template.TemplatePayload;
import io.flamingock.internal.common.core.recovery.action.ChangeAction;
import io.flamingock.internal.core.task.loaded.AbstractTemplateLoadedChange;
import io.flamingock.internal.util.FileUtil;
Expand All @@ -35,7 +36,7 @@
* @param <ROLLBACK> the rollback payload type
* @param <T> the type of template loaded change
*/
public abstract class AbstractTemplateExecutableTask<CONFIG, APPLY, ROLLBACK,
public abstract class AbstractTemplateExecutableTask<CONFIG, APPLY extends TemplatePayload, ROLLBACK extends TemplatePayload,
T extends AbstractTemplateLoadedChange<CONFIG, APPLY, ROLLBACK>> extends ReflectionExecutableTask<T> {
protected final Logger logger = FlamingockLoggerFactory.getLogger("TemplateTask");

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
package io.flamingock.internal.core.task.executable;

import io.flamingock.api.template.AbstractChangeTemplate;
import io.flamingock.api.template.TemplatePayload;
import io.flamingock.internal.common.core.error.ChangeExecutionException;
import io.flamingock.internal.common.core.recovery.action.ChangeAction;
import io.flamingock.internal.core.runtime.ExecutionRuntime;
Expand All @@ -31,7 +32,8 @@
* @param <APPLY> the apply payload type
* @param <ROLLBACK> the rollback payload type
*/
public class SimpleTemplateExecutableTask<CONFIG, APPLY, ROLLBACK>
@SuppressWarnings({"rawtypes", "unchecked"})
public class SimpleTemplateExecutableTask<CONFIG, APPLY extends TemplatePayload, ROLLBACK extends TemplatePayload>
extends AbstractTemplateExecutableTask<CONFIG, APPLY, ROLLBACK,
SimpleTemplateLoadedChange<CONFIG, APPLY, ROLLBACK>> {

Expand All @@ -56,10 +58,9 @@ public void rollback(ExecutionRuntime executionRuntime) {
executeInternal(executionRuntime, rollbackMethod);
}

@SuppressWarnings("unchecked")
protected void executeInternal(ExecutionRuntime executionRuntime, Method method) {
try {
AbstractChangeTemplate<CONFIG, APPLY, ROLLBACK> instance = (AbstractChangeTemplate<CONFIG, APPLY, ROLLBACK>)
AbstractChangeTemplate instance = (AbstractChangeTemplate)
executionRuntime.getInstance(descriptor.getConstructor());

instance.setTransactional(descriptor.isTransactional());
Expand Down
Loading
Loading