| 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 {
String getName();
default Collection<EOperation> getOperations(AsmModel asmModel) {
return Collections.emptyList();
}
default boolean async() { return false; }
default boolean terminateOnException() { return true; }
default boolean ignoreDecoratedCall() { return false; }
default Object preCall(EOperation operation, Object payload) {
return payload;
}
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;
}
@Override
public Collection<EOperation> getOperations(AsmModel asmModel) {
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) {
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() {
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) {
MockWebhookInterceptor mock = new MockWebhookInterceptor();
interceptorProvider.addInterceptor(mock);
orderService.createOrder(newOrder);
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