/**
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You 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 org.apache.camel.component.cxf.jaxrs;
import java.lang.reflect.Method;
import java.nio.charset.Charset;
import java.util.HashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import javax.security.auth.Subject;
import javax.ws.rs.client.Entity;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.MultivaluedMap;
import javax.ws.rs.core.Response;
import javax.ws.rs.core.Variant;
import org.apache.camel.Exchange;
import org.apache.camel.Message;
import org.apache.camel.component.cxf.common.header.CxfHeaderHelper;
import org.apache.camel.component.cxf.common.message.CxfConstants;
import org.apache.camel.spi.HeaderFilterStrategy;
import org.apache.camel.spi.HeaderFilterStrategyAware;
import org.apache.camel.util.ExchangeHelper;
import org.apache.cxf.helpers.HttpHeaderHelper;
import org.apache.cxf.jaxrs.impl.MetadataMap;
import org.apache.cxf.jaxrs.model.OperationResourceInfoStack;
import org.apache.cxf.message.MessageContentsList;
import org.apache.cxf.security.LoginSecurityContext;
import org.apache.cxf.security.SecurityContext;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Default strategy to bind between Camel and CXF exchange for RESTful resources.
*
*
* @version
*/
public class DefaultCxfRsBinding implements CxfRsBinding, HeaderFilterStrategyAware {
private static final Logger LOG = LoggerFactory.getLogger(DefaultCxfRsBinding.class);
private HeaderFilterStrategy headerFilterStrategy;
public DefaultCxfRsBinding() {
}
public Object populateCxfRsResponseFromExchange(Exchange camelExchange,
org.apache.cxf.message.Exchange cxfExchange) throws Exception {
// Need to check if the exchange has the exception
if (camelExchange.isFailed() && camelExchange.getException() != null) {
throw camelExchange.getException();
}
org.apache.camel.Message response;
if (camelExchange.getPattern().isOutCapable()) {
if (camelExchange.hasOut()) {
response = camelExchange.getOut();
LOG.trace("Get the response from the out message");
} else {
response = camelExchange.getIn();
LOG.trace("Get the response from the in message as a fallback");
}
} else {
response = camelExchange.getIn();
LOG.trace("Get the response from the in message");
}
return response.getBody();
}
public void populateExchangeFromCxfRsRequest(org.apache.cxf.message.Exchange cxfExchange,
Exchange camelExchange, Method method, Object[] paramArray) {
Message camelMessage = camelExchange.getIn();
//Copy the CXF message header into the Camel inMessage
org.apache.cxf.message.Message cxfMessage = cxfExchange.getInMessage();
CxfHeaderHelper.copyHttpHeadersFromCxfToCamel(headerFilterStrategy, cxfMessage, camelMessage, camelExchange);
// TODO move to CxfHeaderHelper and use header filter strategy and CXF_TO_CAMEL_HEADERS
// setup the charset from content-type header
setCharsetWithContentType(camelExchange);
//copy the protocol header
copyProtocolHeader(cxfMessage, camelMessage, camelMessage.getExchange());
camelMessage.setHeader(CxfConstants.CAMEL_CXF_RS_RESPONSE_CLASS, method.getReturnType());
camelMessage.setHeader(CxfConstants.CAMEL_CXF_RS_RESPONSE_GENERIC_TYPE, method.getGenericReturnType());
copyOperationResourceInfoStack(cxfMessage, camelMessage);
camelMessage.setHeader(CxfConstants.OPERATION_NAME, method.getName());
camelMessage.setHeader(CxfConstants.CAMEL_CXF_MESSAGE, cxfMessage);
camelMessage.setBody(new MessageContentsList(paramArray));
// propagate the security subject from CXF security context
SecurityContext securityContext = cxfMessage.get(SecurityContext.class);
if (securityContext instanceof LoginSecurityContext
&& ((LoginSecurityContext)securityContext).getSubject() != null) {
camelExchange.getIn().getHeaders().put(Exchange.AUTHENTICATION,
((LoginSecurityContext)securityContext).getSubject());
} else if (securityContext != null && securityContext.getUserPrincipal() != null) {
Subject subject = new Subject();
subject.getPrincipals().add(securityContext.getUserPrincipal());
camelExchange.getIn().getHeaders().put(Exchange.AUTHENTICATION, subject);
}
}
protected void setCharsetWithContentType(Exchange camelExchange) {
// setup the charset from content-type header
String contentTypeHeader = ExchangeHelper.getContentType(camelExchange);
if (contentTypeHeader != null) {
String charset = HttpHeaderHelper.findCharset(contentTypeHeader);
String normalizedEncoding = HttpHeaderHelper.mapCharset(charset, Charset.forName("UTF-8").name());
if (normalizedEncoding != null) {
camelExchange.setProperty(Exchange.CHARSET_NAME, normalizedEncoding);
}
}
}
public MultivaluedMap<String, String> bindCamelHeadersToRequestHeaders(Map<String, Object> camelHeaders,
Exchange camelExchange)
throws Exception {
MultivaluedMap<String, String> answer = new MetadataMap<>();
CxfHeaderHelper.propagateCamelHeadersToCxfHeaders(headerFilterStrategy, camelHeaders, answer, camelExchange);
return answer;
}
/**
* This method call Message.getBody({@link MessageContentsList}) to allow
* an appropriate converter to kick in even through we only read the first
* element off the MessageContextList. If that returns null, we check
* the body to see if it is a List or an array and then return the first
* element. If that fails, we will simply return the object.
*/
public Object bindCamelMessageBodyToRequestBody(Message camelMessage, Exchange camelExchange)
throws Exception {
Object request = camelMessage.getBody(MessageContentsList.class);
if (request != null) {
return ((MessageContentsList)request).get(0);
}
request = camelMessage.getBody();
if (request instanceof List) {
request = ((List<?>)request).get(0);
} else if (request != null && request.getClass().isArray()) {
request = ((Object[])request)[0];
}
return request;
}
/**
* We will return an empty Map unless the response parameter is a {@link Response} object.
*/
public Map<String, Object> bindResponseHeadersToCamelHeaders(Object response, Exchange camelExchange)
throws Exception {
Map<String, Object> answer = new HashMap<>();
if (response instanceof Response) {
Map<String, List<Object>> responseHeaders = ((Response) response).getMetadata();
CxfHeaderHelper.propagateCxfHeadersToCamelHeaders(headerFilterStrategy, responseHeaders, answer, camelExchange);
}
return answer;
}
public Entity<Object> bindCamelMessageToRequestEntity(Object body, Message camelMessage, Exchange camelExchange) throws Exception {
if (body == null) {
return null;
}
String contentType = camelMessage.getHeader(Exchange.CONTENT_TYPE, String.class);
if (contentType == null) {
contentType = MediaType.WILDCARD;
}
String contentEncoding = camelMessage.getHeader(Exchange.CONTENT_ENCODING, String.class);
return Entity.entity(body, new Variant(MediaType.valueOf(contentType), Locale.US, contentEncoding));
}
/**
* By default, we just return the response object.
*/
public Object bindResponseToCamelBody(Object response, Exchange camelExchange) throws Exception {
return response;
}
public HeaderFilterStrategy getHeaderFilterStrategy() {
return headerFilterStrategy;
}
public void setHeaderFilterStrategy(HeaderFilterStrategy strategy) {
headerFilterStrategy = strategy;
}
@SuppressWarnings("unchecked")
protected void copyProtocolHeader(org.apache.cxf.message.Message cxfMessage, Message camelMessage, Exchange camelExchange) {
Map<String, List<String>> headers = (Map<String, List<String>>)cxfMessage.get(org.apache.cxf.message.Message.PROTOCOL_HEADERS);
for (Map.Entry<String, List<String>>entry : headers.entrySet()) {
// just make sure the first String element is not null
if (headerFilterStrategy.applyFilterToExternalHeaders(entry.getKey(), entry.getValue(), camelExchange)
|| entry.getValue().isEmpty()) {
LOG.trace("Drop CXF message protocol header: {}={}", entry.getKey(), entry.getValue());
} else {
// just put the first String element, as the complex one is filtered
camelMessage.setHeader(entry.getKey(), entry.getValue().get(0));
}
continue;
}
}
protected void copyOperationResourceInfoStack(org.apache.cxf.message.Message cxfMessage, Message camelMessage) {
OperationResourceInfoStack stack = cxfMessage.get(OperationResourceInfoStack.class);
if (stack != null) {
// make a copy of the operation resource info for looking up the sub resource location
OperationResourceInfoStack copyStack = (OperationResourceInfoStack)stack.clone();
camelMessage.setHeader(CxfConstants.CAMEL_CXF_RS_OPERATION_RESOURCE_INFO_STACK, copyStack);
}
}
}