judo-runtimecxf-integration

star 0

Apache CXF integration for JAX-RS in JUDO applications. Use when working with CXF interceptors, fault handling, exchange ID tracking, authorization, or understanding the HTTP request/response pipeline.

BlackBeltTechnology By BlackBeltTechnology schedule Updated 1/28/2026

name: judo-runtime:cxf-integration description: Apache CXF integration for JAX-RS in JUDO applications. Use when working with CXF interceptors, fault handling, exchange ID tracking, authorization, or understanding the HTTP request/response pipeline. metadata: author: BlackBelt Technology version: "${project.version}"

Apache CXF Integration for JAX-RS

Comprehensive guide to the JUDO CXF integration layer that provides interceptors, fault handling, exchange ID tracking, and role-based authorization for JAX-RS REST endpoints.

Overview

The judo-runtime-core-jaxrs-cxf module bridges Apache CXF with JUDO's runtime to provide:

  • Request/response interceptors for cross-cutting concerns
  • Fault handling and exception mapping
  • Exchange ID generation and propagation for request tracing
  • Role-based authorization using ASM model metadata

Architecture

flowchart TB
    subgraph "HTTP Layer"
        CLIENT["HTTP Client"]
        JETTY["Jetty Server"]
    end
    
    subgraph "CXF Layer"
        SERVLET["CXFServlet"]
        BUS["CXF Bus"]
        
        subgraph "Interceptor Chain"
            direction TB
            IN_INT["IN Interceptors"]
            OUT_INT["OUT Interceptors"]
            FAULT_INT["FAULT Interceptors"]
        end
        
        subgraph "JAX-RS Providers"
            CORS["CrossOriginResourceSharingFilter"]
            CONTENT["SetDefaultContentTypeFilter"]
            EXC_MAP["ExceptionMappers"]
            JSON["JacksonJaxbJsonProvider"]
            PAYLOAD["PayloadMessageBodyWriter"]
        end
    end
    
    subgraph "JUDO CXF Interceptors"
        EXCH_DEC["ExchangeIdDecorator"]
        EXCH_WRITE["ExchangeIdResponseWriter"]
        AUTH["JudoAuthorizingInterceptor"]
        FAULT["FaultInterceptor"]
    end
    
    subgraph "Application Layer"
        APP["JAX-RS Application"]
        DISP["Dispatcher"]
        DAO["DAO Layer"]
    end
    
    CLIENT <--> JETTY
    JETTY <--> SERVLET
    SERVLET <--> BUS
    BUS --> IN_INT
    IN_INT --> APP
    APP --> OUT_INT
    OUT_INT --> BUS
    
    IN_INT -.-> EXCH_DEC
    IN_INT -.-> AUTH
    OUT_INT -.-> EXCH_WRITE
    FAULT_INT -.-> FAULT
    FAULT_INT -.-> EXCH_WRITE
    
    APP --> DISP
    DISP --> DAO

Interceptor Pipeline

sequenceDiagram
    participant Client
    participant CXF as CXF Bus
    participant ExchDec as ExchangeIdDecorator
    participant Auth as JudoAuthorizingInterceptor
    participant App as JAX-RS Resource
    participant ExchWrite as ExchangeIdResponseWriter
    participant Fault as FaultInterceptor
    
    Client->>CXF: HTTP Request
    
    rect rgb(230, 245, 255)
        Note over CXF,Auth: IN Interceptors (Phase.RECEIVE)
        CXF->>ExchDec: handleMessage()
        ExchDec->>ExchDec: Generate UUID
        ExchDec->>ExchDec: Store in Exchange
        ExchDec->>ExchDec: Set MDC
        CXF->>Auth: handleMessage()
        Auth->>Auth: Extract @JudoOperation
        Auth->>Auth: Check roles from ASM
    end
    
    CXF->>App: Invoke Resource Method
    
    alt Success Path
        App-->>CXF: Response
        rect rgb(230, 255, 230)
            Note over CXF,ExchWrite: OUT Interceptors (Phase.POST_LOGICAL)
            CXF->>ExchWrite: handleMessage()
            ExchWrite->>ExchWrite: Add X-Exchange-Id header
        end
        CXF-->>Client: HTTP Response + X-Exchange-Id
    else Error Path
        App--xCXF: Exception
        rect rgb(255, 230, 230)
            Note over CXF,Fault: FAULT Interceptors (Phase.PRE_STREAM)
            CXF->>Fault: handleFault()
            Fault->>Fault: Determine status code
            Fault->>Fault: Set FaultMode
            CXF->>ExchWrite: handleFault()
            ExchWrite->>ExchWrite: Add X-Exchange-Id header
        end
        CXF-->>Client: HTTP Error + X-Exchange-Id
    end

Key Components

ExchangeIdDecorator

Generates and tracks unique exchange IDs for request tracing.

Package: hu.blackbelt.judo.runtime.core.jaxrs.cxf.interceptors

Phase: Phase.RECEIVE (earliest in pipeline)

Responsibilities:

  • Generate UUID for each request
  • Store exchange ID in CXF Exchange object
  • Set MDC (Mapped Diagnostic Context) for logging correlation
// Exchange ID is accessible via:
String exchangeId = (String) message.getExchange().get("exchangeId");

// MDC key for logging:
// "RequestExchangeId"

Response Header: X-Exchange-Id

ExchangeIdResponseWriter

Writes the exchange ID to response headers for client-side correlation.

Package: hu.blackbelt.judo.runtime.core.jaxrs.cxf.interceptors

Phase: Phase.POST_LOGICAL

Behavior:

  • Adds X-Exchange-Id header to all responses
  • Works for both success and fault responses

JudoAuthorizingInterceptor

Role-based authorization using JUDO ASM model metadata.

Package: hu.blackbelt.judo.runtime.core.jaxrs.cxf.interceptors

How It Works:

flowchart TD
    REQ["Incoming Request"] --> CHECK["Check @JudoOperation annotation"]
    CHECK -->|Not Present| ALLOW["Allow Request"]
    CHECK -->|Present| RESOLVE["Resolve operation in ASM"]
    RESOLVE --> EXPOSED["Get 'exposedBy' annotation"]
    EXPOSED --> PUBLIC{"Has public actor?<br/>(no realm)"}
    PUBLIC -->|Yes| ALLOW
    PUBLIC -->|No| ROLES["Extract required roles<br/>(access point FQNames)"]
    ROLES --> VERIFY["CXF verifies against SecurityContext"]
    VERIFY -->|Has Role| ALLOW
    VERIFY -->|Missing Role| DENY["403 Forbidden"]

Configuration:

JudoAuthorizingInterceptor.builder()
    .asmModel(asmModel)  // Required: ASM model for operation resolution
    .build();

FaultInterceptor

Handles exceptions and sets appropriate HTTP status codes.

Package: hu.blackbelt.judo.runtime.core.jaxrs.cxf.interceptors

Phase: Phase.PRE_STREAM

Status Code Mapping:

Exception Type Status Code FaultMode
InternalServerErrorException + Jackson cause 400 Bad Request CHECKED_APPLICATION_FAULT
InternalServerErrorException + RuntimeException 500 Internal Server Error RUNTIME_FAULT
InternalServerErrorException + Checked Exception 400 Bad Request UNCHECKED_APPLICATION_FAULT
ClientException Custom (from exception) CHECKED_APPLICATION_FAULT
RuntimeException 500 Internal Server Error RUNTIME_FAULT
Other Checked Exceptions 400 Bad Request CHECKED_APPLICATION_FAULT

Configuration:

FaultInterceptor interceptor = new FaultInterceptor();
interceptor.setLogException(true);  // Enable detailed exception logging

Guice Integration

The judo-runtime-core-guice-cxf module provides Guice bindings for all components.

JudoCxfModule Configuration

JudoCxfModule.builder()
    // Server configuration
    .cxfJaxRsServerUrl("http://localhost:8080")
    .cxfJaxRsServerPath("api")
    
    // Exchange ID tracking
    .exchangeIdInterceptors(true)  // Enable exchange ID decorators
    
    // JSON configuration
    .cxfSkipDefaultJsonProviderRegistration(false)
    
    // Logging and metrics
    .cxfLoggingEnabled(true)
    .cxfMetricsEnabled(true)
    .cxfLogException(false)  // Log exceptions at INFO level
    
    // Error handling
    .cxfReturnRuntimeExceptions(false)  // Include stack traces
    .cxfIncludeBusinessCause(false)
    
    // CORS configuration
    .corsAllowOrigin("*")
    .corsAllowCredentials(true)
    .corsAllowHeaders("Content-Type,Authorization,X-Judo-SignedIdentifier")
    .corsExposeHeaders("X-Exchange-Id,X-Fault,X-Judo-Count")
    .corsMaxAge(-1)
    
    .build();

Adding Custom Interceptors

Use Guice multibindings with qualifier annotations:

public class MyModule extends AbstractModule {
    @Override
    protected void configure() {
        // IN interceptors (request processing)
        Multibinder.newSetBinder(binder(), Interceptor.class, CxfQualifiers.InInterceptors.class)
            .addBinding().to(MyCustomInInterceptor.class);
        
        // OUT interceptors (response processing)
        Multibinder.newSetBinder(binder(), Interceptor.class, CxfQualifiers.OutInterceptors.class)
            .addBinding().to(MyCustomOutInterceptor.class);
        
        // FAULT interceptors (error processing)
        Multibinder.newSetBinder(binder(), Interceptor.class, CxfQualifiers.FaultInterceptors.class)
            .addBinding().to(MyCustomFaultInterceptor.class);
    }
}

Adding Custom Providers

public class MyModule extends AbstractModule {
    @Override
    protected void configure() {
        Multibinder.newSetBinder(binder(), Object.class, CxfQualifiers.Providers.class)
            .addBinding().to(MyCustomProvider.class);
    }
}

Creating Custom Interceptors

Example: Request Timing Interceptor

package com.example.interceptors;

import org.apache.cxf.interceptor.Fault;
import org.apache.cxf.message.Message;
import org.apache.cxf.phase.AbstractPhaseInterceptor;
import org.apache.cxf.phase.Phase;
import lombok.extern.slf4j.Slf4j;

@Slf4j
public class RequestTimingInterceptor extends AbstractPhaseInterceptor<Message> {
    
    private static final String START_TIME_KEY = "request.startTime";
    
    public RequestTimingInterceptor() {
        super(Phase.RECEIVE);  // Run early in request processing
    }
    
    @Override
    public void handleMessage(Message message) throws Fault {
        message.getExchange().put(START_TIME_KEY, System.currentTimeMillis());
    }
}

@Slf4j
public class ResponseTimingInterceptor extends AbstractPhaseInterceptor<Message> {
    
    private static final String START_TIME_KEY = "request.startTime";
    
    public ResponseTimingInterceptor() {
        super(Phase.POST_LOGICAL);
    }
    
    @Override
    public void handleMessage(Message message) throws Fault {
        Long startTime = (Long) message.getExchange().get(START_TIME_KEY);
        if (startTime != null) {
            long duration = System.currentTimeMillis() - startTime;
            log.info("Request completed in {} ms", duration);
            
            // Optionally add to response headers
            Map<String, List> headers = (Map<String, List>) message.get(Message.PROTOCOL_HEADERS);
            if (headers == null) {
                headers = new HashMap<>();
                message.put(Message.PROTOCOL_HEADERS, headers);
            }
            headers.put("X-Response-Time", Collections.singletonList(duration + "ms"));
        }
    }
}

Example: Custom Authorization Interceptor

package com.example.interceptors;

import org.apache.cxf.interceptor.Fault;
import org.apache.cxf.message.Message;
import org.apache.cxf.phase.AbstractPhaseInterceptor;
import org.apache.cxf.phase.Phase;
import org.apache.cxf.security.SecurityContext;

import javax.ws.rs.core.Response;

public class CustomAuthorizationInterceptor extends AbstractPhaseInterceptor<Message> {
    
    public CustomAuthorizationInterceptor() {
        super(Phase.PRE_INVOKE);  // After RECEIVE, before method invocation
    }
    
    @Override
    public void handleMessage(Message message) throws Fault {
        SecurityContext sc = message.get(SecurityContext.class);
        
        if (sc == null || sc.getUserPrincipal() == null) {
            // Anonymous request - check if endpoint allows it
            String path = (String) message.get(Message.PATH_INFO);
            if (!isPublicPath(path)) {
                throw new Fault(new SecurityException("Authentication required"));
            }
        }
    }
    
    private boolean isPublicPath(String path) {
        return path != null && (
            path.startsWith("/public/") ||
            path.equals("/health") ||
            path.equals("/metrics")
        );
    }
}

CXF Phases Reference

Interceptors execute in specific phases:

flowchart LR
    subgraph "IN Phases (Request)"
        R1["RECEIVE"] --> R2["PRE_STREAM"]
        R2 --> R3["READ"]
        R3 --> R4["PRE_PROTOCOL"]
        R4 --> R5["UNMARSHAL"]
        R5 --> R6["PRE_LOGICAL"]
        R6 --> R7["USER_LOGICAL"]
        R7 --> R8["POST_LOGICAL"]
        R8 --> R9["PRE_INVOKE"]
        R9 --> R10["INVOKE"]
        R10 --> R11["POST_INVOKE"]
    end
flowchart LR
    subgraph "OUT Phases (Response)"
        O1["SETUP"] --> O2["PRE_LOGICAL"]
        O2 --> O3["USER_LOGICAL"]
        O3 --> O4["POST_LOGICAL"]
        O4 --> O5["PRE_MARSHAL"]
        O5 --> O6["MARSHAL"]
        O6 --> O7["PRE_STREAM"]
        O7 --> O8["WRITE"]
        O8 --> O9["SEND"]
    end

Debugging Tips

Enable CXF Logging

<!-- logback.xml -->
<logger name="org.apache.cxf" level="DEBUG"/>
<logger name="hu.blackbelt.judo.runtime.core.jaxrs.cxf" level="DEBUG"/>

Trace Exchange IDs

Look for the X-Exchange-Id header in responses and correlate with logs using RequestExchangeId MDC key:

<!-- logback pattern -->
<pattern>%d{HH:mm:ss.SSS} [%thread] [%X{RequestExchangeId}] %-5level %logger{36} - %msg%n</pattern>

Inspect Interceptor Chain

// In a custom interceptor:
@Override
public void handleMessage(Message message) throws Fault {
    InterceptorChain chain = message.getInterceptorChain();
    for (Interceptor<?> interceptor : chain) {
        log.debug("Interceptor in chain: {}", interceptor.getClass().getName());
    }
}

Related Modules

Module Description
judo-runtime-core-jaxrs Base JAX-RS providers (exception mappers, body writers)
judo-runtime-core-guice-cxf Guice configuration for CXF integration
judo-runtime-core-jaxrs-cxf-server CXF server bootstrap
judo-runtime-core-security Security framework integration

See Also

  • /judo-runtime:dispatcher-architecture - Understanding operation dispatching
  • /judo-runtime:authentication-flow - Security and authentication details
  • Apache CXF Documentation: https://cxf.apache.org/docs/
Install via CLI
npx skills add https://github.com/BlackBeltTechnology/judo-runtime-core --skill judo-runtimecxf-integration
Repository Details
star Stars 0
call_split Forks 0
navigation Branch main
article Path SKILL.md
More from Creator
BlackBeltTechnology
BlackBeltTechnology Explore all skills →