Many resources are needed to download a project. Please understand that we have to compensate our server costs. Thank you in advance. Project price only 1 $
You can buy this project and download/modify it how often you want.
/*
* 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.io.InputStream;
import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import javax.activation.DataHandler;
import javax.activation.DataSource;
import javax.ws.rs.CookieParam;
import javax.ws.rs.FormParam;
import javax.ws.rs.HeaderParam;
import javax.ws.rs.MatrixParam;
import javax.ws.rs.PathParam;
import javax.ws.rs.QueryParam;
import javax.ws.rs.core.MultivaluedMap;
import javax.ws.rs.core.Response;
import javax.ws.rs.core.Response.ResponseBuilder;
import javax.ws.rs.core.Response.Status;
import org.apache.camel.Message;
import org.apache.camel.attachment.AttachmentMessage;
import org.apache.camel.attachment.DefaultAttachment;
import org.apache.camel.spi.HeaderFilterStrategy;
import org.apache.cxf.jaxrs.ext.multipart.Attachment;
import org.apache.cxf.jaxrs.ext.multipart.InputStreamDataSource;
import org.apache.cxf.jaxrs.ext.multipart.Multipart;
import org.apache.cxf.jaxrs.model.URITemplate;
import org.apache.cxf.jaxrs.utils.AnnotationUtils;
import org.apache.cxf.message.Exchange;
import org.apache.cxf.message.MessageContentsList;
/**
* A CXF RS Binding which maps method parameters as Camel IN headers and the payload as the IN message body. It replaces
* the default behaviour of creating a MessageContentsList, which requires the route to process the contents low-level.
*
*
*
* The mapping from CXF to Camel is performed as follows:
*
*
*
JAX-RS Parameter types (@QueryParam, @HeaderParam, @CookieParam, @FormParam, @PathParam, @MatrixParam) are all
* transferred as IN message headers.
*
If a request entity is clearly identified (for example, because it's the only parameter without an annotation),
* it's set as the IN message body. Otherwise, the original {@link MessageContentsList} is preserved as the message
* body.
*
If Multipart is in use, binary parts are mapped as Camel IN message attachments, while any others are mapped as
* IN message headers for convenience. These classes are considered binary: Attachment, DataHandler, DataSource,
* InputStream. Additionally, the original {@link MessageContentsList} is preserved as the message body.
*
*
* For example, the following JAX-RS method signatures are supported, with the specified outcomes:
*
*
* public Response doAction(BusinessObject request);
* Request payload is placed in IN message body, replacing the original {@link MessageContentsList}.
*
*
* public Response doAction(BusinessObject request, @HeaderParam("abcd") String abcd, @QueryParam("defg") String defg);
* Request payload placed in IN message body, replacing the original {@link MessageContentsList}. Both request params
* mapped as IN message headers with names abcd and defg.
*
*
* public Response doAction(@HeaderParam("abcd") String abcd, @QueryParam("defg") String defg);
* Both request params mapped as IN message headers with names abcd and defg. The original
* {@link MessageContentsList} is preserved, even though it only contains the 2 parameters.
*
*
* public Response doAction(@Multipart(value="body1", type="application/json") BusinessObject request, @Multipart(value="image",
* type="image/jpeg") DataHandler image);
* The first parameter is transferred as a POJO in a header named body1, while the second parameter gets
* injected as an attachment with name image. The MIME type is observed by the CXF stack. The IN message body
* is the original {@link MessageContentsList} handed over from CXF.
*
*
* public Response doAction(InputStream abcd);
* The InputStream is unwrapped from the {@link MessageContentsList} and preserved as the IN message body.
*
*
* public Response doAction(DataHandler abcd);
* The DataHandler is unwrapped from the {@link MessageContentsList} and preserved as the IN message body.
*/
public class SimpleCxfRsBinding extends DefaultCxfRsBinding {
/** The JAX-RS annotations to be injected as headers in the IN message */
private static final Set> HEADER_ANNOTATIONS = Collections.unmodifiableSet(
new HashSet<>(
Arrays.asList(
CookieParam.class,
FormParam.class,
PathParam.class,
HeaderParam.class,
MatrixParam.class,
QueryParam.class)));
private static final Set> BINARY_ATTACHMENT_TYPES = Collections.unmodifiableSet(
new HashSet<>(
Arrays.asList(
Attachment.class,
DataHandler.class,
DataSource.class,
InputStream.class)));
private static final Class>[] NO_PARAMETER_TYPES = null;
private static final Object[] NO_PARAMETERS = null;
/** Caches the Method to Parameters associations to avoid reflection with every request */
private Map methodSpecCache = new ConcurrentHashMap<>();
@Override
public void populateExchangeFromCxfRsRequest(
Exchange cxfExchange, org.apache.camel.Exchange camelExchange,
Method method, Object[] paramArray) {
super.populateExchangeFromCxfRsRequest(cxfExchange, camelExchange, method, paramArray);
Message in = camelExchange.getIn();
bindHeadersFromSubresourceLocators(cxfExchange, camelExchange);
MethodSpec spec = methodSpecCache.get(method);
if (spec == null) {
spec = MethodSpec.fromMethod(method);
methodSpecCache.put(method, spec);
}
bindParameters(in, paramArray, spec.paramNames, spec.numberParameters);
bindBody(in, paramArray, spec.entityIndex);
if (spec.multipart) {
transferMultipartParameters(paramArray, spec.multipartNames, spec.multipartTypes, in);
}
}
@Override
public Object populateCxfRsResponseFromExchange(org.apache.camel.Exchange camelExchange, Exchange cxfExchange)
throws Exception {
Object base = super.populateCxfRsResponseFromExchange(camelExchange, cxfExchange);
return buildResponse(camelExchange, base);
}
/**
* Builds the response for the client.
*
* Always returns a JAX-RS {@link Response} object, which gives the user a better control on the response behaviour.
* If the message body is already an instance of {@link Response}, we reuse it and just inject the relevant HTTP
* headers.
*
* @param camelExchange
* @param base
* @return
*/
protected Object buildResponse(org.apache.camel.Exchange camelExchange, Object base) {
Message m = camelExchange.getMessage();
ResponseBuilder response;
// if the body is different to Response, it's an entity; therefore, check
if (base instanceof Response) {
response = Response.fromResponse((Response) base);
} else {
int status = m.getHeader(org.apache.camel.Exchange.HTTP_RESPONSE_CODE, Status.OK.getStatusCode(), Integer.class);
response = Response.status(status);
// avoid using the request MessageContentsList as the entity; it simply doesn't make sense
if (base != null && !(base instanceof MessageContentsList)) {
response.entity(base);
}
}
// Compute which headers to transfer by applying the HeaderFilterStrategy, and transfer them to the JAX-RS Response
Map headersToPropagate = filterCamelHeadersForResponseHeaders(m.getHeaders(), camelExchange);
for (Entry entry : headersToPropagate.entrySet()) {
response.header(entry.getKey(), entry.getValue());
}
return response.build();
}
/**
* Filters the response headers that will be sent back to the client.
*
* The {@link DefaultCxfRsBinding} doesn't filter the response headers according to the
* {@link HeaderFilterStrategy}, so we handle this task in this binding.
*/
protected Map filterCamelHeadersForResponseHeaders(
Map headers,
org.apache.camel.Exchange camelExchange) {
Map answer = new HashMap<>();
for (Map.Entry entry : headers.entrySet()) {
if (getHeaderFilterStrategy().applyFilterToCamelHeaders(entry.getKey(), entry.getValue(), camelExchange)) {
continue;
}
// skip content-length as the simple binding with Response will set correct content-length based
// on the entity set as the Response
if ("content-length".equalsIgnoreCase(entry.getKey())) {
continue;
}
answer.put(entry.getKey(), entry.getValue().toString());
}
return answer;
}
/**
* Transfers path parameters from the full path (including ancestor subresource locators) into Camel IN Message
* Headers.
*/
@SuppressWarnings("unchecked")
protected void bindHeadersFromSubresourceLocators(Exchange cxfExchange, org.apache.camel.Exchange camelExchange) {
MultivaluedMap pathParams
= (MultivaluedMap) cxfExchange.getInMessage().get(URITemplate.TEMPLATE_PARAMETERS);
// return immediately if we have no path parameters
if (pathParams == null || pathParams.size() == 1 && pathParams.containsKey(URITemplate.FINAL_MATCH_GROUP)) {
return;
}
Message m = camelExchange.getIn();
for (Entry> entry : pathParams.entrySet()) {
// skip over the FINAL_MATCH_GROUP which stores the entire path
if (URITemplate.FINAL_MATCH_GROUP.equals(entry.getKey())) {
continue;
}
m.setHeader(entry.getKey(), entry.getValue().get(0));
}
}
/**
* Binds JAX-RS parameter types (@HeaderParam, @QueryParam, @MatrixParam, etc.) to the exchange.
*
* @param in
* @param paramArray
* @param paramNames
* @param numberParameters
*/
protected void bindParameters(Message in, Object[] paramArray, String[] paramNames, int numberParameters) {
if (numberParameters == 0) {
return;
}
for (int i = 0; i < paramNames.length; i++) {
if (paramNames[i] != null) {
in.setHeader(paramNames[i], paramArray[i]);
}
}
}
/**
* Binds the message body.
*
* @param in
* @param paramArray
* @param singleBodyIndex
*/
protected void bindBody(Message in, Object[] paramArray, int singleBodyIndex) {
if (singleBodyIndex == -1) {
return;
}
in.setBody(paramArray[singleBodyIndex]);
}
private void transferMultipartParameters(
Object[] paramArray, String[] multipartNames, String[] multipartTypes, Message in) {
for (int i = 0; i < multipartNames.length; i++) {
if (multipartNames[i] == null || paramArray[i] == null) {
continue;
}
if (BINARY_ATTACHMENT_TYPES.contains(paramArray[i].getClass())) {
transferBinaryMultipartParameter(paramArray[i], multipartNames[i], multipartTypes[i], in);
} else {
in.setHeader(multipartNames[i], paramArray[i]);
}
}
}
private void transferBinaryMultipartParameter(Object toMap, String parameterName, String multipartType, Message in) {
org.apache.camel.attachment.Attachment dh = null;
if (toMap instanceof Attachment) {
dh = createCamelAttachment((Attachment) toMap);
} else if (toMap instanceof DataSource) {
dh = new DefaultAttachment((DataSource) toMap);
} else if (toMap instanceof DataHandler) {
dh = new DefaultAttachment((DataHandler) toMap);
} else if (toMap instanceof InputStream) {
dh = new DefaultAttachment(
new InputStreamDataSource(
(InputStream) toMap, multipartType == null ? "application/octet-stream" : multipartType));
}
if (dh != null) {
in.getExchange().getMessage(AttachmentMessage.class).addAttachmentObject(parameterName, dh);
}
}
private DefaultAttachment createCamelAttachment(Attachment attachment) {
DefaultAttachment camelAttachment = new DefaultAttachment(attachment.getDataHandler());
for (String name : attachment.getHeaders().keySet()) {
for (String value : attachment.getHeaderAsList(name)) {
camelAttachment.addHeader(name, value);
}
}
return camelAttachment;
}
protected static class MethodSpec {
private boolean multipart;
private int numberParameters;
private int entityIndex = -1;
private String[] paramNames;
private String[] multipartNames;
private String[] multipartTypes;
/**
* Processes this method definition and extracts metadata relevant for the binding process.
*
* @param method The Method to process.
* @return A MethodSpec instance representing the method metadata relevant to the Camel binding process.
*/
public static MethodSpec fromMethod(Method method) {
method = AnnotationUtils.getAnnotatedMethod(method.getDeclaringClass(), method);
MethodSpec answer = new MethodSpec();
Annotation[][] annotations = method.getParameterAnnotations();
int paramCount = method.getParameterTypes().length;
answer.paramNames = new String[paramCount];
answer.multipartNames = new String[paramCount];
answer.multipartTypes = new String[paramCount];
// remember the names of parameters to be bound to headers and/or attachments
for (int i = 0; i < paramCount; i++) {
// if the parameter has no annotations, let its array element remain = null
for (Annotation a : annotations[i]) {
// am I a header?
if (HEADER_ANNOTATIONS.contains(a.annotationType())) {
try {
answer.paramNames[i] = (String) a.annotationType().getMethod("value", NO_PARAMETER_TYPES).invoke(a,
NO_PARAMETERS);
answer.numberParameters++;
} catch (Exception e) {
}
}
// am I multipart?
if (Multipart.class.equals(a.annotationType())) {
Multipart multipart = (Multipart) a;
answer.multipart = true;
answer.multipartNames[i] = multipart.value();
answer.multipartTypes[i] = multipart.type();
}
}
}
// if we are not multipart and the number of detected JAX-RS parameters (query, headers, etc.) is less than the number of method parameters
// there's one parameter that will serve as message body
if (!answer.multipart && answer.numberParameters < method.getParameterTypes().length) {
for (int i = 0; i < answer.paramNames.length; i++) {
if (answer.paramNames[i] == null) {
answer.entityIndex = i;
break;
}
}
}
return answer;
}
}
}