judo-runtimecreate-interceptor

star 0

Step-by-step guide to create custom OperationCallInterceptor implementations in JUDO applications. Use when implementing webhooks, audit logging, input validation, result transformation, or any pre/post operation hooks.

BlackBeltTechnology By BlackBeltTechnology schedule Updated 1/28/2026

name: judo-runtime:create-interceptor description: Step-by-step guide to create custom OperationCallInterceptor implementations in JUDO applications. Use when implementing webhooks, audit logging, input validation, result transformation, or any pre/post operation hooks. metadata: author: BlackBelt Technology version: "${project.version}"

Create Operation Interceptor

Guide for creating custom operation interceptors in JUDO applications.

What is an Interceptor?

Interceptors allow you to hook into any operation (CRUD, scripts, SDK methods) to:

  • Transform input before execution
  • React to results after execution
  • Run async side-effects (webhooks, notifications)
  • Replace the operation entirely

Interceptor Flow

sequenceDiagram
    participant Client
    participant Dispatcher
    participant Interceptor
    participant DAO
    
    Client->>Dispatcher: Call operation
    Dispatcher->>Interceptor: preCall(operation, payload)
    Interceptor-->>Dispatcher: Modified payload
    Dispatcher->>DAO: Execute with payload
    DAO-->>Dispatcher: Result
    Dispatcher->>Interceptor: postCall(operation, input, result)
    Interceptor-->>Dispatcher: Modified result
    Dispatcher-->>Client: Response

Extension Point Interface

Implement OperationCallInterceptor:

package hu.blackbelt.judo.runtime.core.dispatcher;

public interface OperationCallInterceptor {
    
    // Required: Unique name for this interceptor
    String getName();
    
    // Optional: Filter which operations to intercept (empty = all)
    default Collection<EOperation> getOperations(AsmModel asmModel) {
        return Collections.emptyList();
    }
    
    // Optional: Run on separate thread, outside transaction
    default boolean async() { return false; }
    
    // Optional: Stop execution if this interceptor throws
    default boolean terminateOnException() { return true; }
    
    // Optional: Skip the original DAO call entirely
    default boolean ignoreDecoratedCall() { return false; }
    
    // Optional: Called before operation executes
    default Object preCall(EOperation operation, Object payload) {
        return payload;
    }
    
    // Optional: Called after operation executes
    default Object postCall(EOperation operation, Object input, Object result) {
        return result;
    }
}

Method Reference

Method Required Default Description
getName() Yes - Unique interceptor identifier
getOperations(AsmModel) No empty list Filter operations to intercept (empty = all)
async() No false Run on separate thread, outside transaction
terminateOnException() No true Stop execution if this interceptor throws
ignoreDecoratedCall() No false Skip the original DAO call entirely
preCall(operation, payload) No passthrough Called before operation executes
postCall(operation, input, result) No passthrough Called after operation executes

Example: Webhook Interceptor

public class WebhookInterceptor implements OperationCallInterceptor {
    
    private final HttpClient httpClient;
    private final String webhookUrl;
    
    public WebhookInterceptor(HttpClient httpClient, String webhookUrl) {
        this.httpClient = httpClient;
        this.webhookUrl = webhookUrl;
    }
    
    @Override
    public String getName() { 
        return "webhook-interceptor"; 
    }
    
    @Override
    public boolean async() { 
        return true; // Non-blocking, runs on separate thread
    }
    
    @Override
    public Collection<EOperation> getOperations(AsmModel asmModel) {
        // Only intercept create operations on Order entity
        return asmModel.getOperations().stream()
            .filter(op -> op.getName().startsWith("create"))
            .filter(op -> op.getEContainingClass().getName().equals("OrderService"))
            .collect(Collectors.toList());
    }
    
    @Override
    public Object postCall(EOperation operation, Object input, Object result) {
        // Fire webhook after successful operation
        httpClient.post(webhookUrl, Map.of(
            "event", operation.getName(),
            "entity", operation.getEContainingClass().getName(),
            "timestamp", Instant.now().toString(),
            "payload", result
        ));
        return result;
    }
}

Registration with Guice

public class MyInterceptorModule extends AbstractModule {
    
    @Override
    protected void configure() {
        // Register interceptor with Multibinder
        Multibinder<OperationCallInterceptor> interceptors = 
            Multibinder.newSetBinder(binder(), OperationCallInterceptor.class);
        
        interceptors.addBinding().to(WebhookInterceptor.class);
    }
    
    @Provides
    @Singleton
    WebhookInterceptor provideWebhookInterceptor(HttpClient httpClient) {
        return new WebhookInterceptor(httpClient, "https://n8n.example.com/webhook/orders");
    }
}

Registration with Spring

@Configuration
public class InterceptorConfig {
    
    @Bean
    public OperationCallInterceptor webhookInterceptor(HttpClient httpClient) {
        return new WebhookInterceptor(httpClient, "https://n8n.example.com/webhook/orders");
    }
}

Testing with JUDO TestKit

See judo-runtime-core-guice-testkit for testing utilities:

@JudoTest
class WebhookInterceptorTest {
    
    @Test
    void shouldFireWebhookOnCreate(TestOperationCallInterceptorProvider interceptorProvider) {
        // Register mock interceptor
        MockWebhookInterceptor mock = new MockWebhookInterceptor();
        interceptorProvider.addInterceptor(mock);
        
        // Execute operation
        orderService.createOrder(newOrder);
        
        // Verify webhook was called
        assertThat(mock.getCapturedEvents()).hasSize(1);
    }
}

Common Patterns

Audit Logging

@Override
public Object postCall(EOperation operation, Object input, Object result) {
    auditLogger.log(
        operation.getName(),
        getCurrentUser(),
        input,
        result
    );
    return result;
}

Input Validation

@Override
public Object preCall(EOperation operation, Object payload) {
    if (payload instanceof Map) {
        validate((Map<?, ?>) payload);
    }
    return payload;
}

Result Transformation

@Override
public Object postCall(EOperation operation, Object input, Object result) {
    if (result instanceof Payload) {
        return enrichPayload((Payload) result);
    }
    return result;
}

See Also

  • agent-docs/extension-points.md - All extension interfaces
  • agent-docs/examples/webhook-interceptor.java.example - Full example
  • judo-runtime-core-guice-testkit - Testing utilities
Install via CLI
npx skills add https://github.com/BlackBeltTechnology/judo-runtime-core --skill judo-runtimecreate-interceptor
Repository Details
star Stars 0
call_split Forks 0
navigation Branch main
article Path SKILL.md
More from Creator
BlackBeltTechnology
BlackBeltTechnology Explore all skills →