본문으로 바로가기 본문 바로가기
WIKI 기술 지식 베이스

CXF

원문 보기 위키 갱신

CXF (camel-cxf)

CXF 컴포넌트는 Apache CXF와 통합해서, CXF에서 호스팅하는 JAX-WS 웹 서비스에 연결할 수 있게 해 줘요. Camel 1.0부터 제공됐고 producer와 consumer를 모두 지원합니다. POJO, PAYLOAD, RAW, CXF_MESSAGE 같은 dataFormat 옵션을 비롯해 MTOM 첨부 파일, 스트리밍, SOAP 헤더 릴레이 같은 기능을 폭넓게 설정할 수 있어요.

출처: 문서

본문

Camel 1.0부터 지원

producer와 consumer 모두 지원

CXF 컴포넌트는 Apache CXF와 통합해서, CXF에서 호스팅하는 JAX-WS 서비스에 연결해 줘요.

팁 CXF를 스트리밍 모드로 사용할 때는 — 아래의 DataFormat 옵션을 확인하고 — 스트림 캐싱 문서도 함께 읽어 보세요.

Maven 사용자는 이 컴포넌트를 사용하려면 pom.xml에 다음 의존성을 추가해야 해요:

<dependency>
    <groupId>org.apache.camel</groupId>
    <artifactId>camel-cxf-soap</artifactId>
    <version>x.x.x</version>
    <!-- use the same version as your Camel core version -->
</dependency>

URI 형식 (URI Format)

이 엔드포인트에는 두 가지 URI 형식이 있어요: cxfEndpoint와 someAddress.

cxf:bean:cxfEndpoint[?options]

여기서 cxfEndpoint는 Spring 빈 레지스트리에 있는 빈을 가리키는 빈 ID예요. 이 URI 형식에서는 엔드포인트 세부 사항 대부분을 빈 정의 쪽에 지정해요.

cxf://someAddress[?options]

여기서 someAddress는 CXF 엔드포인트의 주소를 지정해요. 이 URI 형식에서는 엔드포인트 세부 사항 대부분을 옵션으로 지정해요.

어느 쪽 스타일이든 아래처럼 URI 뒤에 옵션을 붙일 수 있어요:

cxf:bean:cxfEndpoint?wsdlURL=wsdl/hello_world.wsdl&dataFormat=PAYLOAD

옵션 설정 (Configuring Options)

Camel 컴포넌트는 서로 다른 두 단계로 설정해요:

  • 컴포넌트(component) 레벨

  • 엔드포인트(endpoint) 레벨

컴포넌트 옵션 설정 (Configuring Component Options)

컴포넌트 레벨에서는 엔드포인트들이 이어서 물려받는 일반적이고 공유되는 설정을 해요. 가장 상위의 설정 레벨이죠.

예를 들어 컴포넌트에는 보안 설정, 인증용 자격 증명, 네트워크 연결 URL 같은 것들이 있을 수 있어요.

컴포넌트에 따라 옵션이 몇 개뿐인 것도 있고 아주 많은 것도 있어요. 컴포넌트는 보통 널리 쓰이는 기본값이 미리 설정되어 있어서, 몇 가지 옵션만 설정하거나 아예 하나도 건드리지 않아도 되는 경우가 많아요.

컴포넌트는 다음 방법으로 설정할 수 있어요:

  • the Component DSL.

  • 설정 파일(application.properties, *.yaml 파일 등).

  • Java 코드에서 직접.

엔드포인트 옵션 설정 (Configuring Endpoint Options)

엔드포인트는 옵션이 많아서 설정에 시간을 더 쓰게 될 거예요. 이 옵션들이 엔드포인트가 원하는 일을 하도록 커스터마이즈해 줘요. 옵션은 엔드포인트가 컨슈머(from)로 쓰이는지, 프로듀서(to)로 쓰이는지, 둘 다인지에 따라 분류되기도 해요.

엔드포인트 설정은 대부분 엔드포인트 URI에 _path_와 query 파라미터로 바로 하는 경우가 많아요. Endpoint DSL과 DataFormat DSL을 쓰면 자바에서 엔드포인트와 데이터 포맷을 _타입 세이프_하게 설정할 수도 있어요.

옵션을 설정할 때 좋은 습관은 Property Placeholders를 사용하는 거예요.

프로퍼티 플레이스홀더는 이런 장점이 있어요:

  • 하드코딩된 URL, 포트 번호, 민감 정보, 기타 설정을 코드에 박아 넣는 걸 막아 줘요.

  • 설정을 코드 밖으로 분리(externalizing)할 수 있게 해 줘요.

  • 코드가 더 유연하고 재사용 가능해지는 데 도움이 돼요.

다음 두 섹션에서 모든 옵션을 나열해요. 먼저 컴포넌트 옵션, 그다음 엔드포인트 옵션 순서예요.

컴포넌트 옵션 (Component Options)

CXF 컴포넌트는 아래 나열된 옵션들을 지원해요.

Name Description Default Type
bridgeErrorHandler (consumer) 컨슈머를 Camel 라우팅 에러 핸들러(Error Handler)에 브리지할 수 있게 해 줘요. 즉, Camel 컨슈머가 들어오는 메시지를 집어 올리는 동안 등에서 예외가 발생하면(가능한 경우), 그 예외를 메시지로 처리해서 라우팅 에러 핸들러가 다루게 돼요. 중요: 이것은 서드파티 컴포넌트가 예외가 던져졌을 때 Camel에 알릴 수 있도록 허용하는 경우에만 가능해요. 일부 컴포넌트는 내부에서만 처리하기 때문에 bridgeErrorHandler를 쓸 수 없어요. 다른 상황에서는 Camel 컴포넌트를 개선해서 서드파티 컴포넌트에 연결(hook)해 이를 가능하게 만들 수도 있어요. 기본으로 컨슈머는 org.apache.camel.spi.ExceptionHandler로 예외를 처리하는데, WARN 또는 ERROR 레벨로 로그를 남기고 무시해요. false boolean
lazyStartProducer (producer) 프로듀서를 느리게(lazy, 첫 메시지가 올 때) 시작할지 여부예요. 느리게 시작하면, 프로듀서가 시작 중에 실패해서 라우트 시작 자체가 실패하는 상황에서도 CamelContext와 라우트를 시작할 수 있어요. 시작을 늦춰 두면 실패가 발생했을 때 메시지를 라우팅하는 동안 Camel의 라우팅 에러 핸들러로 처리할 수 있죠. 단, 첫 메시지를 처리할 때 프로듀서를 만들고 시작하는 데 시간이 조금 걸려서 전체 처리 시간이 길어질 수 있다는 점은 감안하세요. false boolean
synchronous (producer (advanced)) 동기 처리를 엄격하게 사용하도록 설정해요. false boolean
allowStreaming (advanced) CXF 컴포넌트가 PAYLOAD 모드로 동작할 때, 들어오는 메시지를 DOM Elements로 DOM 파싱할지, 아니면 일부 경우 스트리밍을 가능하게 하는 javax.xml.transform.Source 객체로 페이로드를 그대로 둘지 제어하는 옵션이에요. false Boolean
autowiredEnabled (advanced) 오토와이어링(autowiring)을 활성화할지 여부예요. 레지스트리에서 일치하는 타입의 인스턴스가 딱 하나 있는지 찾아서, 그 인스턴스로 오토와이어링 옵션(옵션이 autowired로 표시되어 있어야 함)을 자동 설정하는 데 쓰여요. JDBC 데이터 소스, JMS 커넥션 팩토리, AWS 클라이언트 등을 자동 설정할 때 사용할 수 있어요. true boolean
headerFilterStrategy (filter) Camel 메시지와 주고받는 헤더를 필터링하려면 커스텀 org.apache.camel.spi.HeaderFilterStrategy를 사용하세요. HeaderFilterStrategy
useGlobalSslContextParameters (security) 전역 SSL 컨텍스트 파라미터 사용을 활성화해요. false boolean

엔드포인트 옵션 (Endpoint Options)

CXF 엔드포인트는 다음 URI 문법으로 설정해요:

cxf:beanId:address

다음 path 및 query 파라미터를 사용해요:

경로 파라미터 (Path Parameters)

Name Description Default Type
beanId (common) 이미 설정된 CxfEndpoint를 찾아 쓰려고 할 때 쓰는 옵션이에요. 반드시 bean: 접두어를 붙여야 해요. String
address (service) 서비스 퍼블리시 주소예요. String

쿼리 파라미터 (Query Parameters)

Name Description Default Type
dataFormat (common)
CXF 엔드포인트가 지원하는 메시지 데이터 타입이에요.

Enum values:

  • PAYLOAD

  • RAW

  • MESSAGE

  • CXF_MESSAGE

  • POJO

| POJO | DataFormat | | wrappedStyle (common) | SOAP 바디에서 파라미터를 어떻게 표현하는지 기술하는 WSDL 스타일이에요. 값을 false로 하면 CXF가 document-literal unwrapped 스타일을, true로 하면 CXF가 document-literal wrapped 스타일을 선택해요. | false | Boolean | | muteException (consumer) | 활성화하면, 컨슈머 쪽에서 Exchange 처리가 실패했을 때 그 예외가 서비스 계약이 선언한 SOAP fault가 아니라면 호출자에게 돌려주는 SOAP fault에 예외의 클래스나 메시지를 담지 않아요. 라우트가 의도적으로 던지는 fault — CXF Fault, {code WebFault} 어노테이션이 붙은 예외, 메시지 바디로 설정된 Throwable — 는 계약의 일부라서 항상 전체 내용이 그대로 반환돼요. | true | boolean | | bridgeErrorHandler (consumer (advanced)) | 컨슈머를 Camel 라우팅 에러 핸들러(Error Handler)에 브리지할 수 있게 해 줘요. 즉, Camel 컨슈머가 들어오는 메시지를 집어 올리는 동안 등에서 예외가 발생하면(가능한 경우), 그 예외를 메시지로 처리해서 라우팅 에러 핸들러가 다루게 돼요. 중요: 이것은 서드파티 컴포넌트가 예외가 던져졌을 때 Camel에 알릴 수 있도록 허용하는 경우에만 가능해요. 일부 컴포넌트는 내부에서만 처리하기 때문에 bridgeErrorHandler를 쓸 수 없어요. 다른 상황에서는 Camel 컴포넌트를 개선해서 서드파티 컴포넌트에 연결(hook)해 이를 가능하게 만들 수도 있어요. 기본으로 컨슈머는 org.apache.camel.spi.ExceptionHandler로 예외를 처리하는데, WARN 또는 ERROR 레벨로 로그를 남기고 무시해요. | false | boolean | | exceptionHandler (consumer (advanced)) | 컨슈머가 커스텀 ExceptionHandler를 쓰게 할 수 있어요. bridgeErrorHandler 옵션이 활성화되어 있으면 이 옵션은 사용되지 않는다는 점에 주의하세요. 기본으로 컨슈머는 예외를 처리하는데, WARN 또는 ERROR 레벨로 로그를 남기고 무시해요. | | ExceptionHandler | | exchangePattern (consumer (advanced)) |

컨슈머가 교환(exchange)을 만들 때 교환 패턴을 설정해요.

Enum values:

  • InOnly

  • InOut

| | ExchangePattern | | cookieHandler (producer) | HTTP 세션을 유지하는 쿠키 핸들러(cookie handler)를 설정해요. | | CookieHandler | | defaultOperationName (producer) | 원격 서비스를 호출하는 CxfProducer가 사용할 기본 operationName을 설정하는 옵션이에요. | | String | | defaultOperationNamespace (producer) | 원격 서비스를 호출하는 CxfProducer가 사용할 기본 operationNamespace를 설정하는 옵션이에요. | | String | | hostnameVerifier (producer) | 사용할 호스트네임 베리파이어예요. 레지스트리에서 HostnameVerifier를 참조하려면 # 표기를 쓰세요. | | HostnameVerifier | | sslContextParameters (producer) | Camel SSL 설정 참조예요. SSL 컨텍스트를 참조하려면 # 표기를 쓰세요. | | SSLContextParameters | | wrapped (producer) | CXF 엔드포인트 프로듀서가 호출할 연산의 종류예요. | false | boolean | | lazyStartProducer (producer (advanced)) | 프로듀서를 느리게(lazy, 첫 메시지가 올 때) 시작할지 여부예요. 느리게 시작하면, 프로듀서가 시작 중에 실패해서 라우트 시작 자체가 실패하는 상황에서도 CamelContext와 라우트를 시작할 수 있어요. 시작을 늦춰 두면 실패가 발생했을 때 메시지를 라우팅하는 동안 Camel의 라우팅 에러 핸들러로 처리할 수 있죠. 단, 첫 메시지를 처리할 때 프로듀서를 만들고 시작하는 데 시간이 조금 걸려서 전체 처리 시간이 길어질 수 있다는 점은 감안하세요. | false | boolean | | synchronous (producer (advanced)) | 동기 처리를 엄격하게 사용하도록 설정해요. | false | boolean | | allowStreaming (advanced) | CXF 컴포넌트가 PAYLOAD 모드로 동작할 때, 들어오는 메시지를 DOM Elements로 DOM 파싱할지, 아니면 일부 경우 스트리밍을 가능하게 하는 javax.xml.transform.Source 객체로 페이로드를 그대로 둘지 제어하는 옵션이에요. | false | Boolean | | bus (advanced) | 커스텀으로 설정된 CXF Bus를 사용하세요. | | Bus | | continuationTimeout (advanced) | CXF continuation 타임아웃을 설정하는 옵션이에요. CXF 서버가 Jetty나 Servlet 트랜스포트를 사용할 때 CxfConsumer에서 기본으로 사용할 수 있어요. | 30000 | long | | cxfBinding (advanced) | Camel Message와 CXF Message 사이의 바인딩을 제어하려면 커스텀 CxfBinding을 사용하세요. | | CxfBinding | | cxfConfigurer (advanced) | 이 옵션은 org.apache.camel.component.cxf.CxfEndpointConfigurer 구현체를 적용해서 CXF 엔드포인트를 프로그래밍 방식으로 설정할 수 있게 해 줘요. CxfEndpointConfigurer의 configure{ServerClient} 메서드를 구현해서 CXF 서버와 클라이언트를 설정할 수 있어요. | | CxfConfigurer | | defaultBus (advanced) | CXF 엔드포인트가 스스로 bus를 만들 때 기본 bus를 설정해요. | false | boolean | | headerFilterStrategy (advanced) | Camel 메시지와 주고받는 헤더를 필터링하려면 커스텀 HeaderFilterStrategy를 사용하세요. | | HeaderFilterStrategy | | mergeProtocolHeaders (advanced) | 프로토콜 헤더를 병합할지 여부예요. 활성화하면 Camel과 CXF 사이의 헤더 전파가 더 일관되고 비슷해져요. 자세한 내용은 CAMEL-6393을 참고하세요. | false | boolean | | mtomEnabled (advanced) | MTOM(첨부 파일)을 활성화해요. POJO 또는 PAYLOAD 데이터 포맷 모드를 사용해야 해요. | false | boolean | | properties (advanced) | Map의 키/값 쌍으로 추가 CXF 옵션을 설정해요. 예를 들어 SOAP fault에 스택트레이스를 켜려면 properties.faultStackTraceEnabled=true처럼 쓸 수 있어요. 접두어가 붙는 멀티 값 옵션이에요: properties. | | Map | | schemaValidationEnabled (advanced) | 요청과 응답에 대한 스키마 검증을 활성화해요. 성능 때문에 기본은 비활성화예요. | false | Boolean | | skipPayloadMessagePartCheck (advanced) | SOAP 메시지 검증을 비활성화할지 여부를 설정해요. | false | boolean | | loggingFeatureEnabled (logging) | 인바운드·아웃바운드 SOAP 메시지를 로그에 기록하는 CXF Logging Feature를 활성화하는 옵션이에요. | false | boolean | | loggingSizeLimit (logging) | 로깅 기능이 활성화됐을 때 로거가 출력할 총 바이트 수 제한이에요. -1이면 제한이 없어요. | 49152 | int | | skipFaultLogging (logging) | PhaseInterceptorChain이 잡아 낸 Fault의 로깅을 건너뛸지 여부를 제어하는 옵션이에요. | false | boolean | | password (security) | CXF 클라이언트의 기본 인증 비밀번호를 설정하는 옵션이에요. | | String | | username (security) | CXF 클라이언트의 기본 인증 사용자 이름을 설정하는 옵션이에요. | | String | | bindingId (service) | 사용할 서비스 모델의 bindingId예요. | | String | | portName (service) | 이 서비스가 구현하는 엔드포인트 이름으로, wsdl:portname에 대응해요. ns:PORT_NAME 형식이며, ns는 이 범위에서 유효한 네임스페이스 접두어예요. | | String | | publishedEndpointUrl (service) | WSDL에서 퍼블리시되는 endpointUrl을 덮어쓸 수 있는 옵션이에요. 서비스 주소 URL에 wsd를 붙인 주소로 접근할 수 있어요. | | String | | serviceClass (service) | SEI(Service Endpoint Interface) 클래스의 클래스 이름이에요. JSR181 어노테이션이 있어도 없어도 돼요. | | Class | | serviceName (service) | 이 서비스가 구현하는 서비스 이름으로, wsdl:servicename에 대응해요. | | String | | wsdlURL (service) | WSDL의 위치예요. 클래스패스, 파일 시스템, 원격 호스팅 모두 가능해요. | | String |

메시지 헤더 (Message Headers)

CXF 컴포넌트는 아래 나열된 메시지 헤더(들)를 지원해요:

Name Description Default Type
CamelCxfOperationName (common) Constant: OPERATION_NAME 연산의 이름이에요. String
CamelCxfOperationNamespace (common) Constant: OPERATION_NAMESPACE 연산의 네임스페이스예요. String
CamelDestinationOverrideUrl (common) Constant: DESTINATION_OVERRIDE_URL 목적지(destination) 오버라이드 URL이에요. String
ResponseContext (common) Constant: RESPONSE_CONTEXT 응답 컨텍스트예요. Map
CamelAuthentication (common) Constant: AUTHENTICATION 인증 정보예요. Subject
RequestContext (common) Constant: REQUEST_CONTEXT 요청 컨텍스트예요. Object

serviceName과 portName은 QNames라서, 값을 지정할 때는 위 예제에서 보듯이 반드시 {namespace} 접두어를 붙여야 해요.

데이터 포맷 설명 (Descriptions of the data formats)

Apache Camel에서 Camel CXF 컴포넌트는 라우트와 웹 서비스를 통합하는 열쇠예요. Camel CXF 컴포넌트로 CXF 엔드포인트를 만들 수 있는데, 이 엔드포인트는 다음 두 가지 방식으로 사용할 수 있어요:

  • Consumer — (라우트 시작 지점에서) 라우트와 통합되는 웹 서비스 인스턴스를 나타내요. 라우트에 주입되는 페이로드 타입은 엔드포인트의 dataFormat 옵션 값에 따라 달라져요.

  • Producer — (라우트의 다른 지점에서) 현재 교환(exchange) 객체를 원격 웹 서비스에 대한 연산 호출로 변환하는 WS 클라이언트 프록시를 나타내요. 현재 교환의 포맷은 엔드포인트의 dataFormat 설정과 맞아야 해요.

DataFormat Description
POJO POJO(Plain Old Java Object)는 대상 서버에서 호출되는 메서드의 Java 파라미터예요. Protocol과 Logical JAX-WS 핸들러를 모두 지원해요.
PAYLOAD PAYLOAD는 CXF 엔드포인트에서 메시지 설정이 적용된 후의 메시지 페이로드(soap:body 내용)예요. Protocol JAX-WS 핸들러만 지원하고, Logical JAX-WS 핸들러는 지원하지 않아요.
RAW RAW 모드는 트랜스포트 레이어에서 받은 원시 메시지 스트림을 제공해요. 스트림을 건드리거나 바꿀 수는 없고, 이 DataFormat을 사용하면 일부 CXF 인터셉터가 제거되기 때문에 Camel CXF 컨슈머 이후로는 SOAP 헤더를 볼 수 없어요. JAX-WS 핸들러는 지원되지 않아요. RAW 모드는 폐기 예정(deprecated)인 MESSAGE 모드와 동등하다는 점에 주의하세요.
CXF_MESSAGE CXF_MESSAGE는 트랜스포트 레이어의 메시지를 원시(raw) SOAP 메시지로 변환해서 CXF 인터셉터의 모든 기능을 발휘할 수 있게 해 줘요.

교환(exchange)의 데이터 포맷 모드는 교환 프로퍼티 CamelCXFDataFormat을 읽어서 알 수 있어요. 교환 키 상수는 org.apache.camel.component.cxf.common.message.CxfConstants.DATA_FORMAT_PROPERTY에 정의되어 있어요.

HTTP 트랜스포트 (HTTP Transport)

CXF로 SOAP-WS를 호스팅하면 CXF가 HTTP 서버와 통합되어야 해요.

JEE 애플리케이션 서버에서 실행할 때는 Servlet 트랜스포트를 사용할 수 있어요. Java 스탠드얼론 애플리케이션에서 실행할 때는 CXF가 Jetty나 Undertow 사용을 지원해요.

  • cxf-rt-transports-http-jetty JAR

  • cxf-rt-transports-http-undertow JAR

Spring Boot나 Quarkus와 함께 CXF를 사용하면 내장 HTTP 서버와 자동으로 통합돼요.

자세한 내용은 CXF Transport 문서를 참고하세요.

사용 방법 (Usage)

RAW 모드 (RAW Mode)

메시지를 전혀 처리하지 않기 때문에 첨부 파일(attachments)은 지원되지 않아요.

CXF_MESSAGE 모드 (CXF_MESSAGE Mode)

MTOM이 지원되고, 위에서 언급한 Camel Message API로 첨부 파일을 꺼낼 수 있어요. 멀티파트(즉, MTOM) 메시지를 받을 때 기본 SOAPMessag`e to `String 컨버터는 바디에 멀티파트 페이로드 전체를 담아 준다는 점에 주의하세요. SOAP XML만 String으로 필요하다면 message.getSOAPPart()로 메시지 바디를 설정하면 되고, 나머지 작업은 Camel 컨버터가 알아서 해 줘요.

PAYLOAD 모드에서의 스트리밍 지원 (Streaming Support in PAYLOAD mode)

Camel CXF 컴포넌트는 이제 PAYLOAD 모드를 사용할 때 들어오는 메시지의 스트리밍을 지원해요. 예전에는 들어오는 메시지가 완전히 DOM 파싱됐죠. 큰 메시지에서는 시간도 많이 걸리고 메모리도 상당히 사용됐어요. 이제는 들어오는 메시지가 라우팅되는 동안 javax.xml.transform.Source 상태로 남아 있을 수 있고, 페이로드를 아무도 수정하지 않으면 대상 목적지로 바로 스트리밍해 보낼 수도 있어요. 흔한 "단순 프록시" 사용 사례(예: from("cxf:…").to("cxf:…"))에서는 성능이 크게 올라가고 메모리 요구량도 크게 줄어들 수 있어요.

하지만 스트리밍이 적절하지 않거나 바람직하지 않은 경우도 있어요. 스트리밍 특성상 잘못된 들어오는 XML이 처리 체인의 뒤쪽에서야 잡힐 수도 있어요. 그리고 어떤 작업(WS-Security나 메시지 추적 같은)은 어차피 메시지를 DOM 파싱해야 해서 스트리밍의 이점이 제한되기도 해요. 현재 스트리밍을 제어하는 방법은 두 가지예요:

  • 엔드포인트 프로퍼티: 엔드포인트 프로퍼티에 allowStreaming=false를 추가하면 스트리밍을 켜고 끌 수 있어요.

  • 컴포넌트 프로퍼티: CxfComponent 객체에도 allowStreaming 프로퍼티가 있어서, 그 컴포넌트에서 만들어지는 엔드포인트의 기본값을 정할 수 있어요.

전역 시스템 프로퍼티: org.apache.camel.component.cxf.streaming 시스템 프로퍼티를 false로 추가하면 스트리밍이 꺼져요. 이렇게 하면 전역 기본값이 설정되는데, 위의 엔드포인트 프로퍼티를 설정하면 그 엔드포인트에서는 이 값이 덮어 씌워져요.

범용 CXF Dispatch 모드 사용 (Using the generic CXF Dispatch mode)

Camel CXF 컴포넌트는 임의의 구조를 가진 메시지(즉, 특정 XML 스키마에 묶이지 않은 메시지)를 전송할 수 있는 범용 CXF dispatch 모드를 지원해요. 이 모드를 사용하려면 CXF 엔드포인트의 wsdlURL과 serviceClass 속성을 지정하지 않으면 돼요.

import org.apache.camel.component.cxf.common.DataFormat;
import org.apache.camel.component.cxf.jaxws.CxfEndpoint;
import jakarta.enterprise.context.SessionScoped;
import jakarta.enterprise.inject.Produces;
import jakarta.inject.Named;

...

@Produces
@SessionScoped
@Named
CxfEndpoint dispatchEndpoint() {
    final CxfEndpoint result = new CxfEndpoint();
    result.setDataFormat(DataFormat.PAYLOAD);
    result.setAddress("/SoapAnyPort");
    return result;
}
<cxf:cxfEndpoint id="dispatchEndpoint" address="http://localhost:9000/SoapContext/SoapAnyPort">
    <cxf:properties>
        <entry key="dataFormat" value="PAYLOAD"/>
    </cxf:properties>
</cxf:cxfEndpoint>

기본 CXF dispatch 클라이언트는 특정 SOAPAction 헤더를 보내지 않는다는 점에 유의하세요. 따라서 대상 서비스가 특정 SOAPAction 값을 필요로 한다면, 키가 SOAPAction(대소문자 구분 없음)인 Camel 헤더로 값을 공급하면 돼요.

RAW 모드에서 CXF LoggingOutInterceptor 활성화하기 (How to enable CXF's LoggingOutInterceptor in RAW mode)

CXF의 LoggingOutInterceptor는 와이어(wire)로 나가는 아웃바운드 메시지를 로깅 시스템(Java Util Logging)에 출력해요. LoggingOutInterceptor는 PRE_STREAM 페이즈에 속하지만(RAW 모드에서는 PRE_STREAM 페이즈가 제거돼요), LoggingOutInterceptor가 WRITE 페이즈에서 동작하도록 설정해야 해요. 다음은 그 예제예요.

Java 전용: RAW 모드에서 WRITE 페이즈로 LoggingOutInterceptor 설정하기

import java.util.List;
import org.apache.camel.builder.RouteBuilder;
import org.apache.camel.component.cxf.common.DataFormat;
import org.apache.camel.component.cxf.jaxws.CxfEndpoint;
import org.apache.cxf.interceptor.LoggingOutInterceptor;
import org.apache.cxf.phase.Phase;
import jakarta.enterprise.context.SessionScoped;
import jakarta.enterprise.inject.Produces;
import jakarta.inject.Named;

...

@Produces
@SessionScoped
@Named
CxfEndpoint soapMtomEnabledServerPayloadModeEndpoint() {
    final CxfEndpoint result = new CxfEndpoint();
    result.setServiceClass(HelloService.class);
    result.setDataFormat(DataFormat.RAW);
    result.setOutFaultInterceptors(List.of(new LoggingOutInterceptor(Phase.WRITE)));;
    result.setAddress("/helloworld");
    return result;
}

Spring XML에서 같은 설정을 하면 이렇게 돼요:

<bean id="loggingOutInterceptor" class="org.apache.cxf.interceptor.LoggingOutInterceptor">
    <!--  it really should have been user-prestream, but CXF does have such a phase! -->
    <constructor-arg value="write"/>
</bean>

<cxf:cxfEndpoint id="serviceEndpoint" address="http://localhost:${CXFTestSupport.port2}/LoggingInterceptorInMessageModeTest/helloworld"
    serviceClass="org.apache.camel.component.cxf.HelloService">
    <cxf:outInterceptors>
        <ref bean="loggingOutInterceptor"/>
    </cxf:outInterceptors>
    <cxf:properties>
        <entry key="dataFormat" value="RAW"/>
    </cxf:properties>
</cxf:cxfEndpoint>

CxfHeaderFilterStrategy 옵션 설명 (Description of CxfHeaderFilterStrategy options)

JAXWS WSDL-우선 개발자의 관점에서 보면, 와이어상의 in-band 헤더와 out-of-band 헤더가 있어요.

in-band 헤더는 SOAP 헤더처럼 엔드포인트의 WSDL 바인딩 계약에 명시적으로 정의된 헤더예요.

out-of-band 헤더는 와이어로 직렬화되지만 WSDL 바인딩 계약에 명시적으로 속하지는 않는 헤더예요.

헤더 릴레이/필터링은 양방향이에요.

라우트에 CXF 엔드포인트가 있고, 개발자가 SOAP 헤더 같은 와이어상의 헤더를 라우트를 따라 릴레이해서 예를 들어 다른 JAXWS 엔드포인트가 소비하게 만들고 싶다면, CXF 엔드포인트에 CxfHeaderFilterStrategy 인스턴스를 설정하고 그 인스턴스의 relayHeaders 프로퍼티를 true(기본값)로 설정하면 돼요. 게다가 CxfHeaderFilterStrategy 인스턴스는 MessageHeaderFilter 인터페이스 목록도 갖고 있는데, 특정 헤더를 릴레이할지 말지는 이 목록이 결정해요.

여기서 헤더를 릴레이/드롭하는 방법을 보여 주는 테스트를 살펴 보세요:

CxfMessageHeadersRelayTest

  • relayHeaders=true는 헤더를 릴레이하겠다는 의사 표현이에요. 특정 헤더를 릴레이할지에 대한 실제 판단은 MessageHeaderFilter 인터페이스를 구현한 플러거블 인스턴스에 위임돼요. 구체적인 MessageHeaderFilter 구현체에 물어봐서 헤더를 릴레이할지 말지 결정하는 거죠. 이미 잘 알려진 SOAP 네임스페이스에 자신을 바인딩하는 SoapMessageHeaderFilter 구현체가 있어요. 런타임이 모르는 네임스페이스를 가진 헤더가 와이어에 있으면 그냥 릴레이돼요.

  • POJO와 PAYLOAD 모드가 지원돼요. POJO 모드에서는 in-band 헤더가 CXF에 의해 이미 처리돼서 헤더 목록에서 제거되었기 때문에, out-of-band 메시지 헤더만 필터링 대상으로 쓸 수 있어요. POJO 모드에서 in-band 헤더는 MessageContentList에 통합돼요. Camel CXF 컴포넌트는 MessageContentList에서 in-band 헤더를 제거하려고 시도하지 않아요. in-band 헤더를 필터링해야 한다면 PAYLOAD 모드를 사용하거나 (꽤 단순한) CXF 인터셉터/JAXWS Handler를 CXF 엔드포인트에 붙이세요. 다음은 CxfHeaderFilterStrategy를 설정하는 예제예요.

XML 전용: CxfHeaderFilterStrategy의 Spring 빈 정의

<bean id="dropAllMessageHeadersStrategy" class="org.apache.camel.component.cxf.transport.header.CxfHeaderFilterStrategy">

    <!--  Set relayHeaders to false to drop all SOAP headers -->
    <property name="relayHeaders" value="false"/>

</bean>

그러면 엔드포인트가 CxfHeaderFilterStrategy를 참조할 수 있어요:

from("cxf:bean:routerNoRelayEndpoint?headerFilterStrategy=#dropAllMessageHeadersStrategy")
    .to("cxf:bean:serviceNoRelayEndpoint?headerFilterStrategy=#dropAllMessageHeadersStrategy");
<route>
    <from uri="cxf:bean:routerNoRelayEndpoint?headerFilterStrategy=#dropAllMessageHeadersStrategy"/>
    <to uri="cxf:bean:serviceNoRelayEndpoint?headerFilterStrategy=#dropAllMessageHeadersStrategy"/>
</route>
- route:
    from:
      uri: cxf:bean:routerNoRelayEndpoint
      parameters:
        headerFilterStrategy: "#dropAllMessageHeadersStrategy"
      steps:
        - to:
            uri: cxf:bean:serviceNoRelayEndpoint
            parameters:
              headerFilterStrategy: "#dropAllMessageHeadersStrategy"
  • 자신만의 MessageHeaderFilter 구현체를 붙여서 릴레이 목록을 덮어쓰거나 추가할 수 있어요. 미리 로드된 릴레이 인스턴스를 덮어쓰려면, 덮어쓰려는 대상과 같은 네임스페이스를 다루도록 MessageHeaderFilter 구현체를 만들어야 해요.

다음은 사용자 정의 메시지 헤더 필터를 설정하는 예제예요:

<bean id="customMessageFilterStrategy" class="org.apache.camel.component.cxf.transport.header.CxfHeaderFilterStrategy">
    <property name="messageHeaderFilters">
        <list>
            <!--  SoapMessageHeaderFilter is the built-in filter.  It can be removed by omitting it. -->
            <bean class="org.apache.camel.component.cxf.common.header.SoapMessageHeaderFilter"/>

            <!--  Add custom filter here -->
            <bean class="org.apache.camel.component.cxf.soap.headers.CustomHeaderFilter"/>
        </list>
    </property>
</bean>
  • relayHeaders 외에도 다음 프로퍼티들을 CxfHeaderFilterStrategy에서 설정할 수 있어요.
Name Required Description
relayHeaders No 모든 메시지 헤더가 메시지 헤더 필터에 의해 처리돼요 Type: boolean Default: true
relayAllMessageHeaders No 모든 메시지 헤더가 (메시지 헤더 필터의 처리 없이) 전파돼요 Type: boolean Default: false
allowFilterNamespaceClash No 두 필터가 활성화 네임스페이스에서 겹칠 때 어떻게 처리할지 이 프로퍼티가 결정해요. 값이 true면 마지막 것이 이기고, 값이 false면 예외를 던져요 Type: boolean Default: false

Camel CXF 컴포넌트가 java.util.logging 대신 log4j를 쓰게 만드는 방법 (How to make the Camel CXF component use log4j instead of java.util.logging)

CXF의 기본 로거는 java.util.logging이에요. log4j로 바꾸고 싶다면 다음 절차를 따르세요. 클래스패스에 META-INF/cxf/org.apache.cxf.logger라는 이름의 파일을 만드세요. 이 파일에는 org.apache.cxf.common.logging.Log4jLogger 클래스의 완전한 이름(fully qualified name)을 주석 없이 한 줄로 담으면 돼요.

Camel CXF 응답이 XML 처리 지시자로 시작하게 만드는 방법 (How to let Camel CXF response start with xml processing instruction)

PHP 같은 SOAP 클라이언트를 사용하면 CXF가 XML 처리 지시자 <?xml version="1.0" encoding="utf-8"?>를 붙이지 않기 때문에 이런 에러를 만나게 돼요:

Error:sendSms: SoapFault exception: [Client] looks like we got no XML document in [...]

이 문제를 해결하려면, 아래의 WriteXmlDeclarationInterceptor처럼 XML 시작 문서를 대신 쓰도록 StaxOutInterceptor에게 알려 줘야 해요:

Java 전용: 응답에 XML 선언을 강제하는 CXF 인터셉터

public class WriteXmlDeclarationInterceptor extends AbstractPhaseInterceptor<SoapMessage> {
    public WriteXmlDeclarationInterceptor() {
        super(Phase.PRE_STREAM);
        addBefore(StaxOutInterceptor.class.getName());
    }

    public void handleMessage(SoapMessage message) throws Fault {
        message.put("org.apache.cxf.stax.force-start-document", Boolean.TRUE);
    }

}

대안으로, CxfConsumerTest에서 보여 주는 것처럼 메시지 헤더를 추가하는 방법도 있어요:

Java 전용: 응답 컨텍스트 헤더로 XML 선언 강제하기

 // set up the response context which force start document
 Map<String, Object> map = new HashMap<String, Object>();
 map.put("org.apache.cxf.stax.force-start-document", Boolean.TRUE);
 exchange.getMessage().setHeader(Client.RESPONSE_CONTEXT, map);

Spring으로 CXF 엔드포인트 설정 (Configure the CXF endpoints with Spring)

아래 보이는 Spring 설정 파일로 CXF 엔드포인트를 설정할 수 있고, 엔드포인트를 camelContext 태그 안에 넣을 수도 있어요. 서비스 엔드포인트를 호출할 때는 CamelCxfOperationName과 CamelCxfOperationNamespace 헤더를 설정해서 어떤 연산을 호출하는지 명시적으로 알려 줄 수 있어요.

XML 전용: CXF 엔드포인트 빈과 Camel 라우트가 포함된 Spring XML 설정

<beans xmlns="http://www.springframework.org/schema/beans"
        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xmlns:cxf="http://camel.apache.org/schema/cxf"
        xsi:schemaLocation="
        http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
        http://camel.apache.org/schema/cxf http://camel.apache.org/schema/cxf/camel-cxf.xsd
        http://camel.apache.org/schema/spring http://camel.apache.org/schema/spring/camel-spring.xsd">
     <cxf:cxfEndpoint id="routerEndpoint" address="http://localhost:9003/CamelContext/RouterPort"
            serviceClass="org.apache.hello_world_soap_http.GreeterImpl"/>
     <cxf:cxfEndpoint id="serviceEndpoint" address="http://localhost:9000/SoapContext/SoapPort"
            wsdlURL="testutils/hello_world.wsdl"
            serviceClass="org.apache.hello_world_soap_http.Greeter"
            endpointName="s:SoapPort"
            serviceName="s:SOAPService"
        xmlns:s="http://apache.org/hello_world_soap_http" />
     <camelContext id="camel" xmlns="http://camel.apache.org/schema/spring">
       <route>
         <from uri="cxf:bean:routerEndpoint" />
         <to uri="cxf:bean:serviceEndpoint" />
       </route>
    </camelContext>
  </beans>

루트 beans 엘리먼트에 지정된 JAX-WS schemaLocation 속성을 반드시 포함하세요. 이렇게 해야 CXF가 파일을 검증할 수 있고, 필수 사항이에요. 또 <cxf:cxfEndpoint/> 태그 끝의 네임스페이스 선언들도 눈여겨 보세요. 이 태그의 속성 값에서는 {namespace}localName 결합 문법이 아직 지원되지 않기 때문에 이 선언들이 필요해요.

cxf:cxfEndpoint 엘리먼트는 이 외에도 많은 속성을 지원해요:

Name Value
PortName 이 서비스가 구현하는 엔드포인트 이름으로, wsdl:port@name에 대응해요. ns:PORT_NAME 형식이며, ns는 이 범위에서 유효한 네임스페이스 접두어예요.
serviceName 이 서비스가 구현하는 서비스 이름으로, wsdl:service@name에 대응해요. ns:SERVICE_NAME 형식이며, ns는 이 범위에서 유효한 네임스페이스 접두어예요.
wsdlURL WSDL의 위치예요. 클래스패스, 파일 시스템, 원격 호스팅 모두 가능해요.
bindingId 사용할 서비스 모델의 bindingId예요.
address 서비스 퍼블리시 주소예요.
bus JAX-WS 엔드포인트에서 사용할 bus 이름이에요.
serviceClass SEI(Service Endpoint Interface) 클래스의 클래스 이름이에요. JSR181 어노테이션이 있어도 없어도 돼요.

그리고 많은 자식 엘리먼트도 지원해요:

Name Value
cxf:inInterceptors 이 엔드포인트의 인바운드 인터셉터예요. <bean>이나 <ref> 목록.
cxf:inFaultInterceptors 이 엔드포인트의 인바운드 fault 인터셉터예요. <bean>이나 <ref> 목록.
cxf:outInterceptors 이 엔드포인트의 아웃바운드 인터셉터예요. <bean>이나 <ref> 목록.
cxf:outFaultInterceptors 이 엔드포인트의 아웃바운드 fault 인터셉터예요. <bean>이나 <ref> 목록.
cxf:properties JAX-WS 엔드포인트에 공급해야 하는 프로퍼티 맵이에요. 아래를 참고하세요.
cxf:handlers JAX-WS 엔드포인트에 공급해야 하는 JAX-WS 핸들러 목록이에요. 아래를 참고하세요.
cxf:dataBinding 엔드포인트에서 어떤 DataBinding을 사용할지 지정할 수 있어요. Spring <bean class="MyDataBinding"/> 문법으로 공급할 수 있어요.
cxf:binding 이 엔드포인트가 사용할 BindingFactory를 지정할 수 있어요. Spring <bean class="MyBindingFactory"/> 문법으로 공급할 수 있어요.
cxf:features 이 엔드포인트의 인터셉터를 담고 있는 기능(features)들이에요. 빈이나 ref 목록.
cxf:schemaLocations 엔드포인트가 사용할 스키마 위치들이에요. schemaLocation 목록.
cxf:serviceFactory 이 엔드포인트가 사용할 서비스 팩토리예요. Spring <bean class="MyServiceFactory"/> 문법으로 공급할 수 있어요.

인터셉터, 프로퍼티, 핸들러를 제공하는 더 발전된 예제는 CXF JAX-WS Configuration 페이지에서 찾을 수 있어요.

참고 cxf:properties를 사용하면 Spring 설정 파일에서 Camel CXF 엔드포인트의 dataFormat과 setDefaultBus 프로퍼티를 설정할 수 있어요.

<cxf:cxfEndpoint id="testEndpoint" address="http://localhost:9000/router"
     serviceClass="org.apache.camel.component.cxf.HelloService"
     endpointName="s:HelloPort"
     serviceName="s:HelloService"
     xmlns:s="http://www.example.com/test">
     <cxf:properties>
       <entry key="dataFormat" value="RAW"/>
       <entry key="setDefaultBus" value="true"/>
     </cxf:properties>
   </cxf:cxfEndpoint>

예제 (Examples)

POJO 데이터 포맷으로 Camel CXF 엔드포인트의 메시지 소비 방법 (How to consume a message from a Camel CXF endpoint in POJO data format)

Camel CXF 엔드포인트 컨슈머의 POJO 데이터 포맷은 CXF invoker에 기반을 두고 있어서, 메시지 헤더에는 CamelCxfOperationName이라는 이름의 프로퍼티가 있고 메시지 바디는 SEI 연산 파라미터의 목록이에요.

간단한 Java 웹 서비스 인터페이스가 있다고 해 보죠:

Java 전용: JAX-WS 웹 서비스 인터페이스 정의

package org.apache.camel.component.cxf.soap.server;

import jakarta.jws.WebService;

@WebService(targetNamespace = "http://server.soap.cxf.component.camel.apache.org/", name = "TextService", serviceName = "TextService")
public interface TextService {

    String upperCase(String text);

    String lowerCase(String text);
}

그러면 가장 단순한 CXF 서비스를 다음과 같이 만들 수 있어요 (POJO 모드가 기본 모드라서 따로 지정하지 않았다는 점에 유의하세요):

Java 전용: MessageContentsList 처리를 하는 CXF POJO 모드

from("cxf:textServiceResponseFromRoute?serviceClass=org.apache.camel.component.cxf.soap.server.TextService&address=/text-route")
    .process(exchange -> {
        String operation = (String) exchange.getIn().getHeader("CamelCxfOperationName");
        String inputArg = ((MessageContentsList) exchange.getIn().getBody()).get(0).toString();
        String result = null;
        if (operation.equals("upperCase")) {
            result = inputArg.toUpperCase();
        } else if (operation.equals("lowerCase")) {
            result = inputArg.toLowerCase();
        }
        exchange.getIn().setBody(result);
    });

POJO 데이터 포맷으로 Camel CXF 엔드포인트에 보낼 메시지 준비 방법 (How to prepare the message for the Camel CXF endpoint in POJO data format)

Camel CXF 엔드포인트 프로듀서는 CXF client API에 기반을 두고 있어요. 먼저 메시지 헤더에 연산 이름을 지정하고, 메서드 파라미터를 목록에 담은 다음, 이 파라미터 목록으로 메시지를 초기화해요. 응답 메시지의 바디는 MessageContentsList라서 그 목록에서 결과를 꺼낼 수 있어요.

메시지 헤더에 연산 이름을 지정하지 않으면 CxfProducer는 CxfEndpoint의 defaultOperationName을 사용하려고 해요. CxfEndpoint에 defaultOperationName이 설정되어 있지 않으면 연산 목록에서 첫 번째 operationName을 집어 올려요.

메시지 바디에서 객체 배열을 꺼내고 싶다면, CxfProducerRouterTest.testInvokingSimpleServerWithParams에서 보듯이 message.getBody(Object[].class)로 바디를 가져오면 돼요:

Java 전용: ProducerTemplate으로 POJO 모드 요청 준비하고 보내기

Exchange senderExchange = new DefaultExchange(context, ExchangePattern.InOut);
final List<String> params = new ArrayList<>();
// Prepare the request message for the camel-cxf procedure
params.add(TEST_MESSAGE);
senderExchange.getIn().setBody(params);
senderExchange.getIn().setHeader("CamelCxfOperationName", ECHO_OPERATION);

Exchange exchange = template.send("direct:EndpointA", senderExchange);

org.apache.camel.Message out = exchange.getMessage();
// The response message's body is a MessageContentsList which first element is the return value of the operation,
// If there are some holder parameters, the holder parameter will be filled in the reset of List.
// The result will be extracted from the MessageContentsList with the String class type
MessageContentsList result = (MessageContentsList) out.getBody();
LOG.info("Received output text: " + result.get(0));
Map<String, Object> responseContext = CastUtils.cast((Map<?, ?>) out.getHeader(Client.RESPONSE_CONTEXT));
assertNotNull(responseContext);
assertEquals("UTF-8", responseContext.get(org.apache.cxf.message.Message.ENCODING),
        "We should get the response context here");
assertEquals("echo " + TEST_MESSAGE, result.get(0), "Reply body on Camel is wrong");

PAYLOAD 데이터 포맷으로 Camel CXF 엔드포인트의 메시지 소비 방법 (How to consume a message from a Camel CXF endpoint in PAYLOAD data format)

PAYLOAD는 SOAP 엔벨로프의 페이로드를 네이티브 CxfPayload로 처리한다는 뜻이에요. Message.getBody()는 SOAP 메시지 헤더와 SOAP 바디용 getter를 갖춘 org.apache.camel.component.cxf.CxfPayload 객체를 반환해요.

CxfConsumerPayloadTest를 참고하세요:

Java 전용: CxfPayload API로 CXF PAYLOAD 소비하고 처리하기

from(simpleEndpointURI + "&dataFormat=PAYLOAD").to("log:info").process(exchange -> {
    CxfPayload<SoapHeader> requestPayload = exchange.getIn().getBody(CxfPayload.class);
    List<Source> inElements = requestPayload.getBodySources();
    List<Source> outElements = new ArrayList<>();
    String request = exchange.getIn().getBody(String.class);
    XmlConverter converter = new XmlConverter();
    String documentString = ECHO_RESPONSE;

    Element in = new XmlConverter().toDOMElement(inElements.get(0));
    if (!in.getNamespaceURI().equals(ELEMENT_NAMESPACE)) {
        throw new IllegalArgumentException("Wrong element namespace");
    }
    if (in.getLocalName().equals("echoBoolean")) {
        documentString = ECHO_BOOLEAN_RESPONSE;
        checkRequest("ECHO_BOOLEAN_REQUEST", request);
    } else {
        documentString = ECHO_RESPONSE;
        checkRequest("ECHO_REQUEST", request);
    }
    Document outDocument = converter.toDOMDocument(documentString, exchange);
    outElements.add(new DOMSource(outDocument.getDocumentElement()));
    CxfPayload<SoapHeader> responsePayload = new CxfPayload<>(null, outElements, null);
    exchange.getMessage().setBody(responsePayload);
});

POJO 모드에서 SOAP 헤더 읽고 쓰기 (How to get and set SOAP headers in POJO mode)

POJO는 Camel CXF 엔드포인트가 Camel 교환(exchange)을 생산하거나 소비할 때 데이터 포맷이 "자바 객체 목록" 이라는 뜻이에요. 이 모드에서 Camel이 메시지 바디를 POJO로 노출하더라도, Camel CXF는 여전히 SOAP 헤더를 읽고 쓸 수 있게 해 줘요. 다만 CXF 인터셉터가 in-band SOAP 헤더를 처리한 뒤 헤더 목록에서 제거해 버리기 때문에, POJO 모드에서 Camel CXF가 접근할 수 있는 건 out-of-band SOAP 헤더뿐이에요.

다음 예제는 SOAP 헤더를 읽고 쓰는 방법을 보여 줘요. 한 Camel CXF 엔드포인트에서 다른 엔드포인트로 포워딩하는 라우트가 있다고 해 보죠. 즉 SOAP Client → Camel → CXF service 순서예요. (1) 요청이 CXF 서비스로 나가기 전과 (2) 응답이 SOAP Client에게 돌아오기 전에 SOAP 헤더를 얻거나 삽입하는 프로세서 두 개를 붙일 수 있어요. 이 예제에서 프로세서 (1)과 (2)는 InsertRequestOutHeaderProcessor와 InsertResponseOutHeaderProcessor예요. 라우트는 이렇게 생겼어요:

from("cxf:bean:routerRelayEndpointWithInsertion")
    .process(new InsertRequestOutHeaderProcessor())
    .to("cxf:bean:serviceRelayEndpointWithInsertion")
    .process(new InsertResponseOutHeaderProcessor());
<route>
    <from uri="cxf:bean:routerRelayEndpointWithInsertion"/>
    <process ref="InsertRequestOutHeaderProcessor" />
    <to uri="cxf:bean:serviceRelayEndpointWithInsertion"/>
    <process ref="InsertResponseOutHeaderProcessor" />
</route>
- route:
    from:
      uri: cxf:bean:routerRelayEndpointWithInsertion
      steps:
        - process:
            ref: InsertRequestOutHeaderProcessor
        - to:
            uri: cxf:bean:serviceRelayEndpointWithInsertion
        - process:
            ref: InsertResponseOutHeaderProcessor

SOAP 헤더는 Camel 메시지 헤더로 오가며 전파돼요. Camel 메시지 헤더 이름은 org.apache.cxf.headers.Header.list이고, CXF(org.apache.cxf.headers.Header.HEADER_LIST)에서 정의한 상수예요. 헤더 값은 CXF SoapHeader 객체(org.apache.cxf.binding.soap.SoapHeader)의 List예요. 다음 스니펫은 InsertResponseOutHeaderProcessor(응답 메시지에 새 SOAP 헤더를 삽입하는 프로세서)예요. InsertResponseOutHeaderProcessor와 InsertRequestOutHeaderProcessor에서 SOAP 헤더에 접근하는 방법은 사실 같아요. 두 프로세서의 유일한 차이는 삽입되는 SOAP 헤더의 방향을 설정한다는 점이에요.

InsertResponseOutHeaderProcessor 예제는 CxfMessageHeadersRelayTest에서 찾을 수 있어요:

Java 전용: 응답에 out-of-band SOAP 헤더를 삽입하는 Processor

public static class InsertResponseOutHeaderProcessor implements Processor {

    public void process(Exchange exchange) throws Exception {
        List<SoapHeader> soapHeaders = CastUtils.cast((List<?>)exchange.getIn().getHeader(Header.HEADER_LIST));

        // Insert a new header
        String xml = "<?xml version=\"1.0\" encoding=\"utf-8\"?><outofbandHeader "
            + "xmlns=\"http://cxf.apache.org/outofband/Header\" hdrAttribute=\"testHdrAttribute\" "
            + "xmlns:soap=\"http://schemas.xmlsoap.org/soap/envelope/\" soap:mustUnderstand=\"1\">"
            + "<name>New_testOobHeader</name><value>New_testOobHeaderValue</value></outofbandHeader>";
        SoapHeader newHeader = new SoapHeader(soapHeaders.get(0).getName(),
                       DOMUtils.readXml(new StringReader(xml)).getDocumentElement());
        // make sure the direction is OUT since it is a response message.
        newHeader.setDirection(Direction.DIRECTION_OUT);
        //newHeader.setMustUnderstand(false);
        soapHeaders.add(newHeader);

    }

}

PAYLOAD 모드에서 SOAP 헤더 읽고 쓰기 (How to get and set SOAP headers in PAYLOAD mode)

POJO 데이터 포맷으로 Camel CXF 엔드포인트의 메시지 소비 방법 섹션에서 이미 PAYLOAD 모드에서 SOAP 메시지를 CxfPayload 객체로 접근하는 방법을 봤어요.

CxfPayload 객체를 얻고 나면, DOM Elements(SOAP 헤더)의 List를 반환하는 CxfPayload.getHeaders() 메서드를 호출할 수 있어요.

예를 들어 CxfPayLoadSoapHeaderTest를 참고하세요:

Java 전용: PAYLOAD 모드에서 CxfPayload로 SOAP 헤더에 접근하기

from(getRouterEndpointURI()).process(exchange -> {
    CxfPayload<SoapHeader> payload = exchange.getIn().getBody(CxfPayload.class);
    List<Source> elements = payload.getBodySources();
    assertNotNull(elements, "We should get the elements here");
    assertEquals(1, elements.size(), "Get the wrong elements size");

    Element el = new XmlConverter().toDOMElement(elements.get(0));
    elements.set(0, new DOMSource(el));
    assertEquals("http://camel.apache.org/pizza/types",
            el.getNamespaceURI(), "Get the wrong namespace URI");

    List<SoapHeader> headers = payload.getHeaders();
    assertNotNull(headers, "We should get the headers here");
    assertEquals(1, headers.size(), "Get the wrong headers size");
    assertEquals("http://camel.apache.org/pizza/types",
            ((Element) (headers.get(0).getObject())).getNamespaceURI(), "Get the wrong namespace URI");
    headers = exchange.getIn().getHeader(Header.HEADER_LIST, List.class);
    assertNotNull(headers, "We should get the headers here");
    assertEquals(1, headers.size(), "Get the wrong headers size");
    assertEquals("http://camel.apache.org/pizza/types",
            ((Element) (headers.get(0).getObject())).getNamespaceURI(), "Get the wrong namespace URI");
})
.to(getServiceEndpointURI());

"POJO 모드에서 SOAP 헤더 읽고 쓰기" 하위 장에서 설명한 것과 같은 방법으로 SOAP 헤더를 설정하거나 읽을 수도 있어요. 즉, 헤더 org.apache.cxf.headers.Header.list를 사용해서 SOAP 헤더 목록을 읽고 쓸 수 있다는 거죠. 이는 또한 한 Camel CXF 엔드포인트에서 다른 엔드포인트로 포워딩하는 라우트(SOAP Client → Camel → CXF service)가 있을 때, SOAP 클라이언트가 보낸 SOAP 헤더도 CXF 서비스로 전달된다는 뜻이에요. 이 헤더들이 전달되기를 원하지 않는다면 Camel 헤더 org.apache.cxf.headers.Header.list에서 제거해야 해요.

RAW 모드에서는 SOAP 헤더를 사용할 수 없어요 (SOAP headers are not available in RAW mode)

SOAP 처리가 생략되기 때문에 RAW 모드에서는 SOAP 헤더를 사용할 수 없어요.

Camel에서 SOAP Fault 던지는 방법 (How to throw a SOAP Fault from Camel)

Camel CXF 엔드포인트로 SOAP 요청을 소비할 때는 camel context에서 SOAP Fault를 던져야 할 수 있어요.
기본적으로는 메시지 바디에 SoapFault를 설정하면 되는데, POJO, PAYLOAD, RAW 데이터 포맷 모두에서 동작해요.
CxfCustomizedExceptionTest에서 보는 것처럼 SOAP fault를 정의할 수 있어요:

Java 전용: detail 텍스트가 있는 SoapFault 만들기

SOAP_FAULT = new SoapFault(EXCEPTION_MESSAGE, SoapFault.FAULT_CODE_CLIENT);
Element detail = SOAP_FAULT.getOrCreateDetail();
Document doc = detail.getOwnerDocument();
Text tn = doc.createTextNode(DETAIL_TEXT);
detail.appendChild(tn);

그런 다음 원하는 대로 던지면 돼요:

Java 전용: 메시지 바디에 SOAP fault 설정하기

from(routerEndpointURI).process(exchange -> exchange.getMessage().setBody(SOAP_FAULT));

CXF 엔드포인트가 RAW 데이터 포맷으로 동작한다면, CxfMessageStreamExceptionTest에서 보듯이 메시지 바디에 SOAP Fault 메시지를 설정하고 메시지 헤더에 응답 코드를 설정할 수 있어요:

Java 전용: RAW 모드에서 SOAP fault 응답 바디와 HTTP 상태 코드 설정하기

from(routerEndpointURI)
    .process(exchange -> {
        Message out = exchange.getMessage();
        out.setBody(this.getClass().getResourceAsStream("SoapFaultMessage.xml"));
        out.setHeader(org.apache.cxf.message.Message.RESPONSE_CODE, 500);
    });

POJO 데이터 포맷을 사용할 때도 마찬가지예요. OUT 바디에 SOAPFault를 설정하면 돼요.

Camel CXF 엔드포인트의 요청·응답 컨텍스트 전파 방법 (How to propagate a Camel CXF endpoint's request and response context)

CXF client API는 요청·응답 컨텍스트와 함께 연산을 호출하는 방법을 제공해요. Camel CXF 엔드포인트 프로듀서로 외부 웹 서비스를 호출한다면, 다음 코드로 요청 컨텍스트를 설정하고 응답 컨텍스트를 얻을 수 있어요:

Java 전용: ProducerTemplate으로 요청 컨텍스트 설정하고 응답 컨텍스트 읽기

CxfExchange exchange = (CxfExchange)template.send(getJaxwsEndpointUri(), ex -> {
    final List<String> params = new ArrayList<String>();
    params.add(TEST_MESSAGE);
    Map<String, Object> requestContext = new HashMap<String, Object>();
    requestContext.put(BindingProvider.ENDPOINT_ADDRESS_PROPERTY, JAXWS_SERVER_ADDRESS);
    ex.getIn().setBody(params);
    ex.getIn().setHeader(Client.REQUEST_CONTEXT, requestContext);
    ex.getIn().setHeader("CamelCxfOperationName", GREET_ME_OPERATION);
});
org.apache.camel.Message out = exchange.getMessage();
// The output is an object array, the first element of the array is the return value
Object\[\] output = out.getBody(Object\[\].class);
LOG.info("Received output text: " + output\[0\]);
// Get the response context form outMessage
Map<String, Object> responseContext = CastUtils.cast((Map)out.getHeader(Client.RESPONSE_CONTEXT));
assertNotNull(responseContext);
assertEquals("Get the wrong wsdl operation name", "{http://apache.org/hello_world_soap_http}greetMe",
    responseContext.get("javax.xml.ws.wsdl.operation").toString());

첨부 파일 지원 (Attachment Support)

POJO 모드 (POJO Mode)

MTOM이 활성화되어 있으면 Message Transmission Optimization Mechanism(MTOM)을 지원해요 — MTOM 활성화 예제는 Payload 모드 섹션을 확인하세요. 첨부 파일은 POJO로 마샬링/언마샬링되기 때문에 Apache Camel 메시지 바디(파라미터 목록)에서 꺼내야 하고, Camel Message API로는 첨부 파일을 꺼낼 수 없어요.

Java 전용: 교환에서 ID로 첨부 파일 꺼내기

DataHandler handler = Exchange.getIn(AttachmentMessage.class).getAttachment("id");

Payload 모드 (Payload Mode)

이 모드는 Message Transmission Optimization Mechanism(MTOM)을 지원해요. 위에서 언급한 Camel Message API로 첨부 파일을 꺼낼 수 있어요. SOAP with Attachment(SwA)도 지원되고 첨부 파일을 꺼낼 수 있어요. SwA가 기본이에요(CXF 엔드포인트 프로퍼티 mtomEnabled를 false로 설정한 것과 같아요).

MTOM을 활성화하려면 CXF 엔드포인트 프로퍼티 mtomEnabled를 true로 설정하세요.

Payload 모드로 CXF 엔드포인트에 보낼 첨부 파일이 담긴 Camel 메시지를 만들 수도 있어요.

Java 전용: ProducerTemplate으로 MTOM 첨부 파일 주고받기

Exchange exchange = context.createProducerTemplate().send("direct:testEndpoint", ex -> {
    ex.setPattern(ExchangePattern.InOut);
    List<Source> elements = new ArrayList<Source>();
    elements.add(new DOMSource(DOMUtils.readXml(new StringReader(MtomTestHelper.REQ_MESSAGE)).getDocumentElement()));
    CxfPayload<SoapHeader> body = new CxfPayload<SoapHeader>(new ArrayList<SoapHeader>(),
        elements, null);
    ex.getIn().setBody(body);
    ex.getIn(AttachmentMessage.class).addAttachment(MtomTestHelper.REQ_PHOTO_CID,
        new DataHandler(new ByteArrayDataSource(MtomTestHelper.REQ_PHOTO_DATA, "application/octet-stream")));
    ex.getIn(AttachmentMessage.class).addAttachment(MtomTestHelper.REQ_IMAGE_CID,
        new DataHandler(new ByteArrayDataSource(MtomTestHelper.requestJpeg, "image/jpeg")));
});

// process response

CxfPayload<SoapHeader> out = exchange.getMessage().getBody(CxfPayload.class);
assertEquals(1, out.getBody().size());

Map<String, String> ns = new HashMap<>();
ns.put("ns", MtomTestHelper.SERVICE_TYPES_NS);
ns.put("xop", MtomTestHelper.XOP_NS);

XPathUtils xu = new XPathUtils(ns);
Element oute = new XmlConverter().toDOMElement(out.getBody().get(0));
Element ele = (Element) xu.getValue("//ns:DetailResponse/ns:photo/xop:Include", oute,
                XPathConstants.NODE);
String photoId = ele.getAttribute("href").substring(4); // skip "cid:"

ele = (Element) xu.getValue("//ns:DetailResponse/ns:image/xop:Include", oute,
                XPathConstants.NODE);
String imageId = ele.getAttribute("href").substring(4); // skip "cid:"

DataHandler dr = exchange.getMessage(AttachmentMessage.class).getAttachment(decodingReference(photoId));
assertEquals("application/octet-stream", dr.getContentType());
assertArrayEquals(MtomTestHelper.RESP_PHOTO_DATA, IOUtils.readBytesFromStream(dr.getInputStream()));

dr = exchange.getMessage(AttachmentMessage.class).getAttachment(decodingReference(imageId));
assertEquals("image/jpeg", dr.getContentType());

BufferedImage image = ImageIO.read(dr.getInputStream());
assertEquals(560, image.getWidth());
assertEquals(300, image.getHeight());

Payload 모드로 CXF 엔드포인트에서 받은 Camel 메시지를 소비할 수도 있어요. CxfMtomConsumerPayloadModeTest가 어떻게 동작하는지 보여 줘요:

Java 전용: MTOM 요청을 검증하고 응답하는 Processor

public static class MyProcessor implements Processor {

    @Override
    @SuppressWarnings("unchecked")
    public void process(Exchange exchange) throws Exception {
        CxfPayload<SoapHeader> in = exchange.getIn().getBody(CxfPayload.class);

        // verify request
        assertEquals(1, in.getBody().size());

        Map<String, String> ns = new HashMap<>();
        ns.put("ns", MtomTestHelper.SERVICE_TYPES_NS);
        ns.put("xop", MtomTestHelper.XOP_NS);

        XPathUtils xu = new XPathUtils(ns);
        Element body = new XmlConverter().toDOMElement(in.getBody().get(0));
        Element ele = (Element) xu.getValue("//ns:Detail/ns:photo/xop:Include", body,
                XPathConstants.NODE);
        String photoId = ele.getAttribute("href").substring(4); // skip "cid:"
        assertEquals(MtomTestHelper.REQ_PHOTO_CID, photoId);

        ele = (Element) xu.getValue("//ns:Detail/ns:image/xop:Include", body,
                XPathConstants.NODE);
        String imageId = ele.getAttribute("href").substring(4); // skip "cid:"
        assertEquals(MtomTestHelper.REQ_IMAGE_CID, imageId);

        DataHandler dr = exchange.getIn(AttachmentMessage.class).getAttachment(photoId);
        assertEquals("application/octet-stream", dr.getContentType());
        assertArrayEquals(MtomTestHelper.REQ_PHOTO_DATA, IOUtils.readBytesFromStream(dr.getInputStream()));

        dr = exchange.getIn(AttachmentMessage.class).getAttachment(imageId);
        assertEquals("image/jpeg", dr.getContentType());
        assertArrayEquals(MtomTestHelper.requestJpeg, IOUtils.readBytesFromStream(dr.getInputStream()));

        // create response
        List<Source> elements = new ArrayList<>();
        elements.add(new DOMSource(StaxUtils.read(new StringReader(MtomTestHelper.RESP_MESSAGE)).getDocumentElement()));
        CxfPayload<SoapHeader> sbody = new CxfPayload<>(
                new ArrayList<SoapHeader>(),
                elements, null);
        exchange.getMessage().setBody(sbody);
        exchange.getMessage(AttachmentMessage.class).addAttachment(MtomTestHelper.RESP_PHOTO_CID,
                new DataHandler(new ByteArrayDataSource(MtomTestHelper.RESP_PHOTO_DATA, "application/octet-stream")));

        exchange.getMessage(AttachmentMessage.class).addAttachment(MtomTestHelper.RESP_IMAGE_CID,
                new DataHandler(new ByteArrayDataSource(MtomTestHelper.responseJpeg, "image/jpeg")));

    }
}

camel-cxf 컨슈머에서 원격 연결 IP 주소 얻는 방법 (How can I get the remote connection IP address from the camel-cxf consumer ?)

메시지 헤더에서 CamelCxfMessage 키로 CXF Message에 접근할 수 있고, CXF 메시지에서 ServletRequest 인스턴스를 꺼낼 수 있어요. 그러면 원격 연결 IP는 쉽게 얻을 수 있죠.

코드 스니펫은 다음과 같아요:

Java 전용: 원격 IP 주소를 얻으려고 CXF 메시지에 접근하기

org.apache.cxf.message.Message cxfMessage = exchange.getIn().getHeader(
        "CamelCxfMessage", org.apache.cxf.message.Message.class);
ServletRequest request = (ServletRequest) cxfMessage.get("HTTP.REQUEST");
String remoteAddress = request.getRemoteAddr();

Spring 설정을 만지지 않고 CXF 컨슈머를 HTTP와 HTTPS 사이에서 전환하는 방법 (How to switch the CXF consumer between HTTP and HTTPS without touching the Spring configuration?)

HTTPS로 Camel CXF 컨슈머를 보호하는 일반적인 정보는 Apache CXF Client HTTP transport 문서에서 찾을 수 있어요.

\http:conduit 설정으로 SSL을 활성화하고, 환경별 설정은 모두 외부 프로퍼티 파일로 뽑아 낸 단순한 Camel CXF 컨슈머 설정은 Spring XML에서 이렇게 생겼을 수 있어요:

Spring XML

<beans xmlns="http://www.springframework.org/schema/beans"
  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xmlns:ctx="http://www.springframework.org/schema/context"
  xmlns:camel="http://camel.apache.org/schema/spring"
  xmlns:camel-cxf="http://camel.apache.org/schema/cxf"
  xmlns:http="http://cxf.apache.org/transports/http/configuration"
  xmlns:sec="http://cxf.apache.org/configuration/security"
  xsi:schemaLocation="
    http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
    http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd
    http://camel.apache.org/schema/spring http://camel.apache.org/schema/spring/camel-spring.xsd
    http://camel.apache.org/schema/osgi http://camel.apache.org/schema/osgi/camel-osgi.xsd
    http://camel.apache.org/schema/cxf http://camel.apache.org/schema/cxf/camel-cxf.xsd
    http://cxf.apache.org/transports/http/configuration http://cxf.apache.org/schemas/configuration/http-conf.xsd
    http://cxf.apache.org/configuration/security http://cxf.apache.org/schemas/configuration/security.xsd
    ">

  <import resource="classpath:META-INF/cxf/cxf.xml" />
  <import resource="classpath:META-INF/cxf/cxf-extension-soap.xml" />
  <import resource="classpath:META-INF/cxf/cxf-extension-http-jetty.xml" />

  <ctx:property-placeholder location="classpath:orderEntry.cfg" />

  <camel-cxf:cxfEndpoint id="orderEntryEndpoint"
    address="${endpointUri}"
    serviceClass="com.company.product.OrderEntryService"
    endpointName="ssp:OrderEntry"
    serviceName="ssp:OrderEntryService"
    wsdlURL="META-INF/orderEntry/orderEntry.wsdl"
    xmlns:ssp="http://www.company.com/product/orderEntry/service/1" />

  <http:conduit name="{http://www.company.com/product/orderEntry/service/1}OrderEntry.http-conduit">
    <http:tlsClientParameters disableCNCheck="true">
      <sec:trustManagers>
        <sec:keyStore type="JKS" password="${trustStore.password}" file="${trustStore.file}"/>
      </sec:trustManagers>
      <sec:cipherSuitesFilter>
        <sec:include>.*_EXPORT_.*</sec:include>
        <sec:include>.*_EXPORT1024_.*</sec:include>
        <sec:include>.*_WITH_DES_.*</sec:include>
        <sec:include>.*_WITH_NULL_.*</sec:include>
        <sec:exclude>.*_DH_anon_.*</sec:exclude>
      </sec:cipherSuitesFilter>
    </http:tlsClientParameters>
  </http:conduit>

  <camel:camelContext trace="true">
    <camel:routeBuilder ref="orderEntryRoute" />
  </camel:camelContext>

  <bean id="orderEntryRoute" class="com.company.product.OrderEntryRoute" />
</beans>

환경별 설정은 프로퍼티 파일로 외부화돼요:

orderEntry.cfg

endpointUri=https://localhost:8181/OrderEntry
trustStore.password=password
trustStore.file=etc/myApp.ts

이 설정으로 Camel CXF 컨슈머는 HTTPS로 웹 서비스 제공자에게 연결해요.

추적/디버깅 같은 이유로 프로토콜을 HTTP로 바꿔야 한다면, 프로퍼티 파일의 endpointUri 프로퍼티를 예를 들어 http://localhost:8080/OrderEntry로 바꾸면 돼요.

Apache CXF가 오직 HTTP만 사용한다는 걸 감지하고, HttpsURLConnectionFactory 대신 HttpURLConnectionFactoryImpl을 인스턴스화해요.

더 알아보기 (Learn more)

CXF 엔드포인트의 옵션 전체 목록은 위의 컴포넌트 옵션·엔드포인트 옵션 섹션에 정리되어 있고, 데이터 포맷별 동작 차이는 데이터 포맷 설명 섹션을 참고하세요. 스트리밍 모드로 사용할 때는 스트림 캐싱 문서를 함께 읽어 보시고, CXF 쪽 심화 설정은 JAX-WS Configuration 페이지와 CXF Transport 문서에서 확인할 수 있어요. 의존성 좌표는 본문 앞부분의 Maven 스니펫 그대로 camel-cxf-soap 아티팩트를 사용하면 돼요.