This is an automated email from the ASF dual-hosted git repository. davsclaus pushed a commit to branch binding in repository https://gitbox.apache.org/repos/asf/camel.git
commit 6d455b136be879908105330eecdbf6ff836726a7 Author: Claus Ibsen <claus.ib...@gmail.com> AuthorDate: Tue Apr 2 12:28:31 2024 +0200 CAMEL-20557: Rest DSL to use openapi spec directly --- ...mHttpRestOpenApiConsumerRestDslBindingTest.java | 4 +- .../DefaultRestOpenapiProcessorStrategy.java | 30 +- .../rest/openapi/RestOpenApiConsumerPath.java | 18 +- .../rest/openapi/RestOpenApiProcessor.java | 120 ++++- .../rest/openapi/RestOpenapiProcessorStrategy.java | 23 +- .../apache/camel/processor/RestBindingAdvice.java | 555 +-------------------- .../camel/reifier/rest/RestBindingReifier.java | 9 +- .../processor/RestBindingConfiguration.java | 171 +++++++ .../support/processor/RestBindingFactory.java} | 115 ++--- .../support/processor/RestBindingSupport.java} | 46 +- 10 files changed, 403 insertions(+), 688 deletions(-) diff --git a/components/camel-platform-http-vertx/src/test/java/org/apache/camel/component/platform/http/vertx/PlatformHttpRestOpenApiConsumerRestDslBindingTest.java b/components/camel-platform-http-vertx/src/test/java/org/apache/camel/component/platform/http/vertx/PlatformHttpRestOpenApiConsumerRestDslBindingTest.java index 45fd9cbac50..ab62516d8c6 100644 --- a/components/camel-platform-http-vertx/src/test/java/org/apache/camel/component/platform/http/vertx/PlatformHttpRestOpenApiConsumerRestDslBindingTest.java +++ b/components/camel-platform-http-vertx/src/test/java/org/apache/camel/component/platform/http/vertx/PlatformHttpRestOpenApiConsumerRestDslBindingTest.java @@ -41,10 +41,12 @@ public class PlatformHttpRestOpenApiConsumerRestDslBindingTest { from("direct:getPetById") .process(e -> { + // build response body as POJO Pet pet = new Pet(); pet.setId(e.getMessage().getHeader("petId", long.class)); pet.setName("tony the tiger"); pet.setStatus(Pet.Status.AVAILABLE); + e.getMessage().setBody(pet); }); } }); @@ -56,7 +58,7 @@ public class PlatformHttpRestOpenApiConsumerRestDslBindingTest { .get("/api/v3/pet/123") .then() .statusCode(200) - .body(equalTo("{\"pet\": \"tony the tiger\"}")); + .body(equalTo("{\"id\":123,\"name\":\"tony the tiger\",\"status\":\"AVAILABLE\"}")); } finally { context.stop(); diff --git a/components/camel-rest-openapi/src/main/java/org/apache/camel/component/rest/openapi/DefaultRestOpenapiProcessorStrategy.java b/components/camel-rest-openapi/src/main/java/org/apache/camel/component/rest/openapi/DefaultRestOpenapiProcessorStrategy.java index f1d47dbc6c8..b6860956d7f 100644 --- a/components/camel-rest-openapi/src/main/java/org/apache/camel/component/rest/openapi/DefaultRestOpenapiProcessorStrategy.java +++ b/components/camel-rest-openapi/src/main/java/org/apache/camel/component/rest/openapi/DefaultRestOpenapiProcessorStrategy.java @@ -20,6 +20,7 @@ import java.io.InputStream; import java.util.ArrayList; import java.util.Collection; import java.util.List; +import java.util.Map; import java.util.stream.Collectors; import io.swagger.v3.oas.models.OpenAPI; @@ -32,7 +33,6 @@ import org.apache.camel.CamelContextAware; import org.apache.camel.Endpoint; import org.apache.camel.Exchange; import org.apache.camel.NonManagedService; -import org.apache.camel.Processor; import org.apache.camel.Route; import org.apache.camel.RuntimeCamelException; import org.apache.camel.component.platform.http.PlatformHttpComponent; @@ -42,6 +42,7 @@ import org.apache.camel.spi.Resource; import org.apache.camel.support.ExchangeHelper; import org.apache.camel.support.PluginHelper; import org.apache.camel.support.cache.DefaultProducerCache; +import org.apache.camel.support.processor.RestBindingSupport; import org.apache.camel.support.service.ServiceHelper; import org.apache.camel.support.service.ServiceSupport; import org.apache.camel.util.FileUtil; @@ -164,36 +165,35 @@ public class DefaultRestOpenapiProcessorStrategy extends ServiceSupport @Override public boolean process( Operation operation, String path, - Processor beforeBinding, Processor afterBinding, + RestBindingSupport binding, Exchange exchange, AsyncCallback callback) { + if ("mock".equalsIgnoreCase(missingOperation)) { // check if there is a route Endpoint e = camelContext.hasEndpoint(component + ":" + operation.getOperationId()); if (e == null) { + // no route then try to load mock data as the answer loadMockData(operation, path, exchange); callback.done(true); return true; } } - if (beforeBinding != null) { - try { - beforeBinding.process(exchange); - } catch (Exception e) { - exchange.setException(e); - callback.done(true); - return true; - } + Map<String, Object> state; + try { + state = binding.before(exchange); + } catch (Exception e) { + exchange.setException(e); + callback.done(true); + return true; } - Endpoint e = camelContext.getEndpoint(component + ":" + operation.getOperationId()); - AsyncProducer p = producerCache.acquireProducer(e); + final Endpoint e = camelContext.getEndpoint(component + ":" + operation.getOperationId()); + final AsyncProducer p = producerCache.acquireProducer(e); return p.process(exchange, doneSync -> { try { producerCache.releaseProducer(e, p); - if (afterBinding != null) { - afterBinding.process(exchange); - } + binding.after(exchange, state); } catch (Exception ex) { exchange.setException(ex); } finally { diff --git a/components/camel-rest-openapi/src/main/java/org/apache/camel/component/rest/openapi/RestOpenApiConsumerPath.java b/components/camel-rest-openapi/src/main/java/org/apache/camel/component/rest/openapi/RestOpenApiConsumerPath.java index f4123ec2f18..6938a42c00b 100644 --- a/components/camel-rest-openapi/src/main/java/org/apache/camel/component/rest/openapi/RestOpenApiConsumerPath.java +++ b/components/camel-rest-openapi/src/main/java/org/apache/camel/component/rest/openapi/RestOpenApiConsumerPath.java @@ -17,24 +17,22 @@ package org.apache.camel.component.rest.openapi; import io.swagger.v3.oas.models.Operation; -import org.apache.camel.Processor; import org.apache.camel.support.RestConsumerContextPathMatcher; +import org.apache.camel.support.processor.RestBindingSupport; class RestOpenApiConsumerPath implements RestConsumerContextPathMatcher.ConsumerPath<Operation> { private final String verb; private final String path; private final Operation consumer; - private final Processor bindingBefore; - private final Processor bindingAfter; + private final RestBindingSupport binding; public RestOpenApiConsumerPath(String verb, String path, Operation consumer, - Processor bindingBefore, Processor bindingAfter) { + RestBindingSupport binding) { this.verb = verb; this.path = path; this.consumer = consumer; - this.bindingBefore = bindingBefore; - this.bindingAfter = bindingAfter; + this.binding = binding; } @Override @@ -57,11 +55,7 @@ class RestOpenApiConsumerPath implements RestConsumerContextPathMatcher.Consumer return false; } - public Processor getBindingBefore() { - return bindingBefore; - } - - public Processor getBindingAfter() { - return bindingAfter; + public RestBindingSupport getBinding() { + return binding; } } diff --git a/components/camel-rest-openapi/src/main/java/org/apache/camel/component/rest/openapi/RestOpenApiProcessor.java b/components/camel-rest-openapi/src/main/java/org/apache/camel/component/rest/openapi/RestOpenApiProcessor.java index a79a1d66f89..e47063cc94b 100644 --- a/components/camel-rest-openapi/src/main/java/org/apache/camel/component/rest/openapi/RestOpenApiProcessor.java +++ b/components/camel-rest-openapi/src/main/java/org/apache/camel/component/rest/openapi/RestOpenApiProcessor.java @@ -21,6 +21,7 @@ import java.util.Arrays; import java.util.List; import java.util.Locale; import java.util.Map; +import java.util.Set; import java.util.function.Function; import java.util.stream.Collectors; @@ -41,6 +42,9 @@ import org.apache.camel.support.ExchangeHelper; import org.apache.camel.support.MessageHelper; import org.apache.camel.support.RestConsumerContextPathMatcher; import org.apache.camel.support.processor.DelegateAsyncProcessor; +import org.apache.camel.support.processor.RestBindingConfiguration; +import org.apache.camel.support.processor.RestBindingFactory; +import org.apache.camel.support.processor.RestBindingSupport; import org.apache.camel.support.service.ServiceHelper; import org.apache.camel.util.ObjectHelper; import org.slf4j.Logger; @@ -94,23 +98,15 @@ public class RestOpenApiProcessor extends DelegateAsyncProcessor implements Came RestConsumerContextPathMatcher.ConsumerPath<Operation> m = RestConsumerContextPathMatcher.matchBestPath(verb, path, paths); - if (m != null) { - Operation o = m.getConsumer(); - - // before and after processors for binding - Processor before = null; - Processor after = null; - if (m instanceof RestOpenApiConsumerPath mp) { - before = mp.getBindingBefore(); - after = mp.getBindingAfter(); - } + if (m instanceof RestOpenApiConsumerPath rcp) { + Operation o = rcp.getConsumer(); // binding mode RestConfiguration config = camelContext.getRestConfiguration(); RestConfiguration.RestBindingMode bindingMode = config.getBindingMode(); // map path-parameters from operation to camel headers - HttpHelper.evalPlaceholders(exchange.getMessage().getHeaders(), path, m.getConsumerPath()); + HttpHelper.evalPlaceholders(exchange.getMessage().getHeaders(), path, rcp.getConsumerPath()); // we have found the op to call, but if validation is enabled then we need // to validate the incoming request first @@ -120,7 +116,7 @@ public class RestOpenApiProcessor extends DelegateAsyncProcessor implements Came } // process the incoming request - return restOpenapiProcessorStrategy.process(o, path, before, after, exchange, callback); + return restOpenapiProcessorStrategy.process(o, path, rcp.getBinding(), exchange, callback); } // is it the api-context path @@ -325,19 +321,101 @@ public class RestOpenApiProcessor extends DelegateAsyncProcessor implements Came super.doBuild(); CamelContextAware.trySetCamelContext(restOpenapiProcessorStrategy, getCamelContext()); + // register all openapi paths for (var e : openAPI.getPaths().entrySet()) { String path = e.getKey(); // path for (var o : e.getValue().readOperationsMap().entrySet()) { String v = o.getKey().name(); // verb - RestOpenApiBindingBefore before = new RestOpenApiBindingBefore(); - RestOpenApiBindingAfter after = new RestOpenApiBindingAfter(); - paths.add(new RestOpenApiConsumerPath(v, path, o.getValue(), before, after)); + // create per operation binding + RestBindingSupport binding = createRestBinding(o.getValue()); + ServiceHelper.buildService(binding); + paths.add(new RestOpenApiConsumerPath(v, path, o.getValue(), binding)); } } ServiceHelper.buildService(restOpenapiProcessorStrategy); } + private RestBindingSupport createRestBinding(Operation o) throws Exception { + RestConfiguration config = camelContext.getRestConfiguration(); + RestConfiguration.RestBindingMode mode = config.getBindingMode(); + + RestBindingConfiguration bc = new RestBindingConfiguration(); + bc.setBindingMode(mode.name()); + bc.setEnableCORS(config.isEnableCORS()); + bc.setCorsHeaders(config.getCorsHeaders()); + bc.setClientRequestValidation(config.isClientRequestValidation()); + bc.setEnableNoContentResponse(config.isEnableNoContentResponse()); + bc.setSkipBindingOnErrorCode(config.isSkipBindingOnErrorCode()); + + String consumes = endpoint.getConsumes(); + String produces = endpoint.getProduces(); + // the operation may have specific information what it can consume + if (o.getRequestBody() != null) { + Content c = o.getRequestBody().getContent(); + if (c != null) { + consumes = c.keySet().stream().sorted().collect(Collectors.joining(",")); + } + } + // the operation may have specific information what it can produce + if (o.getResponses() != null) { + for (var a : o.getResponses().values()) { + Content c = a.getContent(); + if (c != null) { + produces = c.keySet().stream().sorted().collect(Collectors.joining(",")); + } + } + } + bc.setConsumes(consumes); + bc.setProduces(produces); + + boolean requiredBody = false; + if (o.getRequestBody() != null) { + requiredBody = Boolean.TRUE == o.getRequestBody().getRequired(); + } + bc.setRequiredBody(requiredBody); + + Set<String> requiredQueryParameters = null; + if (o.getParameters() != null) { + requiredQueryParameters = o.getParameters().stream() + .filter(p -> "query".equals(p.getIn())) + .filter(p -> Boolean.TRUE == p.getRequired()) + .map(Parameter::getName) + .collect(Collectors.toSet()); + } + if (requiredQueryParameters != null) { + bc.setRequiredQueryParameters(requiredQueryParameters); + } + + Set<String> requiredHeaders = null; + if (o.getParameters() != null) { + requiredHeaders = o.getParameters().stream() + .filter(p -> "header".equals(p.getIn())) + .filter(p -> Boolean.TRUE == p.getRequired()) + .map(Parameter::getName) + .collect(Collectors.toSet()); + } + if (requiredHeaders != null) { + bc.setRequiredHeaders(requiredHeaders); + } + // TODO: should type be string/int/long for basic types? + Map<String, String> defaultQueryValues = null; + if (o.getParameters() != null) { + defaultQueryValues = o.getParameters().stream() + .filter(p -> "query".equals(p.getIn())) + .filter(p -> p.getSchema() != null) + .filter(p -> p.getSchema().getDefault() != null) + .collect(Collectors.toMap(Parameter::getName, p -> p.getSchema().getDefault().toString())); + } + if (defaultQueryValues != null) { + bc.setQueryDefaultValues(defaultQueryValues); + } + + // TODO: type/outType + + return RestBindingFactory.build(camelContext, bc); + } + @Override protected void doInit() throws Exception { super.doInit(); @@ -354,12 +432,22 @@ public class RestOpenApiProcessor extends DelegateAsyncProcessor implements Came protected void doStart() throws Exception { super.doStart(); ServiceHelper.startService(restOpenapiProcessorStrategy); + for (var p : paths) { + if (p instanceof RestOpenApiConsumerPath rcp) { + ServiceHelper.startService(rcp.getBinding()); + } + } } @Override protected void doStop() throws Exception { super.doStop(); - paths.clear(); ServiceHelper.stopService(restOpenapiProcessorStrategy); + for (var p : paths) { + if (p instanceof RestOpenApiConsumerPath rcp) { + ServiceHelper.stopService(rcp.getBinding()); + } + } + paths.clear(); } } diff --git a/components/camel-rest-openapi/src/main/java/org/apache/camel/component/rest/openapi/RestOpenapiProcessorStrategy.java b/components/camel-rest-openapi/src/main/java/org/apache/camel/component/rest/openapi/RestOpenapiProcessorStrategy.java index 8b4c516d310..4a22f91635f 100644 --- a/components/camel-rest-openapi/src/main/java/org/apache/camel/component/rest/openapi/RestOpenapiProcessorStrategy.java +++ b/components/camel-rest-openapi/src/main/java/org/apache/camel/component/rest/openapi/RestOpenapiProcessorStrategy.java @@ -20,7 +20,7 @@ import io.swagger.v3.oas.models.OpenAPI; import io.swagger.v3.oas.models.Operation; import org.apache.camel.AsyncCallback; import org.apache.camel.Exchange; -import org.apache.camel.Processor; +import org.apache.camel.support.processor.RestBindingSupport; /** * Strategy for processing the Rest DSL that services an OpenAPI spec. @@ -64,20 +64,19 @@ public interface RestOpenapiProcessorStrategy { /** * Strategy for processing the Rest DSL operation * - * @param operation the rest operation - * @param path the context-path - * @param bindingBefore optional binding to execute before processing the Rest DSL operation - * @param bindingAfter optional binding to execute after processing the Rest DSL operation - * @param exchange the exchange - * @param callback the AsyncCallback will be invoked when the processing of the exchange is completed. If the - * exchange is completed synchronously, then the callback is also invoked synchronously. The - * callback should therefore be careful of starting recursive loop. - * @return (doneSync) true to continue execute synchronously, false to continue being executed - * asynchronously + * @param operation the rest operation + * @param path the context-path + * @param binding binding advice + * @param exchange the exchange + * @param callback the AsyncCallback will be invoked when the processing of the exchange is completed. If the + * exchange is completed synchronously, then the callback is also invoked synchronously. The + * callback should therefore be careful of starting recursive loop. + * @return (doneSync) true to continue execute synchronously, false to continue being executed + * asynchronously */ boolean process( Operation operation, String path, - Processor bindingBefore, Processor bindingAfter, + RestBindingSupport binding, Exchange exchange, AsyncCallback callback); /** diff --git a/core/camel-core-processor/src/main/java/org/apache/camel/processor/RestBindingAdvice.java b/core/camel-core-processor/src/main/java/org/apache/camel/processor/RestBindingAdvice.java index 0efcf7c0dae..0bb4a6d7406 100644 --- a/core/camel-core-processor/src/main/java/org/apache/camel/processor/RestBindingAdvice.java +++ b/core/camel-core-processor/src/main/java/org/apache/camel/processor/RestBindingAdvice.java @@ -16,30 +16,13 @@ */ package org.apache.camel.processor; -import java.util.HashMap; -import java.util.Locale; import java.util.Map; import java.util.Set; -import org.apache.camel.AsyncProcessor; import org.apache.camel.CamelContext; -import org.apache.camel.CamelExchangeException; -import org.apache.camel.Exchange; -import org.apache.camel.Message; import org.apache.camel.spi.CamelInternalProcessorAdvice; import org.apache.camel.spi.DataFormat; -import org.apache.camel.spi.DataType; -import org.apache.camel.spi.DataTypeAware; -import org.apache.camel.spi.RestConfiguration; -import org.apache.camel.support.ExchangeHelper; -import org.apache.camel.support.MessageHelper; -import org.apache.camel.support.processor.MarshalProcessor; -import org.apache.camel.support.processor.UnmarshalProcessor; -import org.apache.camel.util.ObjectHelper; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import static org.apache.camel.support.http.RestUtil.isValidOrAcceptedContentType; +import org.apache.camel.support.processor.RestBindingSupport; /** * A {@link CamelInternalProcessorAdvice} that binds the REST DSL incoming and outgoing messages from sources of json or @@ -50,539 +33,23 @@ import static org.apache.camel.support.http.RestUtil.isValidOrAcceptedContentTyp * <p/> * The rest producer side is implemented in {@link org.apache.camel.component.rest.RestProducerBindingProcessor} */ -public class RestBindingAdvice implements CamelInternalProcessorAdvice<Map<String, Object>> { - - private static final Logger LOG = LoggerFactory.getLogger(RestBindingAdvice.class); - private static final String STATE_KEY_DO_MARSHAL = "doMarshal"; - private static final String STATE_KEY_ACCEPT = "accept"; - private static final String STATE_JSON = "json"; - private static final String STATE_XML = "xml"; - - private final AsyncProcessor jsonUnmarshal; - private final AsyncProcessor xmlUnmarshal; - private final AsyncProcessor jsonMarshal; - private final AsyncProcessor xmlMarshal; - private final String consumes; - private final String produces; - private final String bindingMode; - private final boolean skipBindingOnErrorCode; - private final boolean clientRequestValidation; - private final boolean enableCORS; - private final boolean enableNoContentResponse; - private final Map<String, String> corsHeaders; - private final Map<String, String> queryDefaultValues; - private final boolean requiredBody; - private final Set<String> requiredQueryParameters; - private final Set<String> requiredHeaders; +@Deprecated +public class RestBindingAdvice extends RestBindingSupport { public RestBindingAdvice(CamelContext camelContext, DataFormat jsonDataFormat, DataFormat xmlDataFormat, DataFormat outJsonDataFormat, DataFormat outXmlDataFormat, - String consumes, String produces, String bindingMode, - boolean skipBindingOnErrorCode, boolean clientRequestValidation, boolean enableCORS, + String consumes, String produces, + String bindingMode, boolean skipBindingOnErrorCode, + boolean clientRequestValidation, boolean enableCORS, boolean enableNoContentResponse, - Map<String, String> corsHeaders, - Map<String, String> queryDefaultValues, + Map<String, String> corsHeaders, Map<String, String> queryDefaultValues, boolean requiredBody, Set<String> requiredQueryParameters, Set<String> requiredHeaders) throws Exception { - if (jsonDataFormat != null) { - this.jsonUnmarshal = new UnmarshalProcessor(jsonDataFormat); - } else { - this.jsonUnmarshal = null; - } - if (outJsonDataFormat != null) { - this.jsonMarshal = new MarshalProcessor(outJsonDataFormat); - } else if (jsonDataFormat != null) { - this.jsonMarshal = new MarshalProcessor(jsonDataFormat); - } else { - this.jsonMarshal = null; - } - - if (xmlDataFormat != null) { - this.xmlUnmarshal = new UnmarshalProcessor(xmlDataFormat); - } else { - this.xmlUnmarshal = null; - } - if (outXmlDataFormat != null) { - this.xmlMarshal = new MarshalProcessor(outXmlDataFormat); - } else if (xmlDataFormat != null) { - this.xmlMarshal = new MarshalProcessor(xmlDataFormat); - } else { - this.xmlMarshal = null; - } - - if (jsonMarshal != null) { - camelContext.addService(jsonMarshal, true); - } - if (jsonUnmarshal != null) { - camelContext.addService(jsonUnmarshal, true); - } - if (xmlMarshal != null) { - camelContext.addService(xmlMarshal, true); - } - if (xmlUnmarshal != null) { - camelContext.addService(xmlUnmarshal, true); - } - - this.consumes = consumes; - this.produces = produces; - this.bindingMode = bindingMode; - this.skipBindingOnErrorCode = skipBindingOnErrorCode; - this.clientRequestValidation = clientRequestValidation; - this.enableCORS = enableCORS; - this.corsHeaders = corsHeaders; - this.queryDefaultValues = queryDefaultValues; - this.requiredBody = requiredBody; - this.requiredQueryParameters = requiredQueryParameters; - this.requiredHeaders = requiredHeaders; - this.enableNoContentResponse = enableNoContentResponse; - } - - @Override - public Map<String, Object> before(Exchange exchange) throws Exception { - Map<String, Object> state = new HashMap<>(); - if (isOptionsMethod(exchange, state)) { - return state; - } - unmarshal(exchange, state); - return state; - } - - @Override - public void after(Exchange exchange, Map<String, Object> state) throws Exception { - if (enableCORS) { - setCORSHeaders(exchange); - } - if (state.get(STATE_KEY_DO_MARSHAL) != null) { - marshal(exchange, state); - } - } - - private boolean isOptionsMethod(Exchange exchange, Map<String, Object> state) { - String method = exchange.getIn().getHeader(Exchange.HTTP_METHOD, String.class); - if ("OPTIONS".equalsIgnoreCase(method)) { - // for OPTIONS methods then we should not route at all as its part of CORS - exchange.setRouteStop(true); - return true; - } - return false; - } - - private void unmarshal(Exchange exchange, Map<String, Object> state) { - boolean isXml = false; - boolean isJson = false; - - String contentType = ExchangeHelper.getContentType(exchange); - if (contentType != null) { - isXml = contentType.toLowerCase(Locale.ENGLISH).contains("xml"); - isJson = contentType.toLowerCase(Locale.ENGLISH).contains("json"); - } - // if content type could not tell us if it was json or xml, then fallback to if the binding was configured with - // that information in the consumes - if (!isXml && !isJson) { - isXml = consumes != null && consumes.toLowerCase(Locale.ENGLISH).contains("xml"); - isJson = consumes != null && consumes.toLowerCase(Locale.ENGLISH).contains("json"); - } - - // set data type if in use - if (exchange.getContext().isUseDataType()) { - if (exchange.getIn() instanceof DataTypeAware && (isJson || isXml)) { - ((DataTypeAware) exchange.getIn()).setDataType(new DataType(isJson ? "json" : "xml")); - } - } - - // only allow xml/json if the binding mode allows that - isXml &= bindingMode.equals("auto") || bindingMode.contains("xml"); - isJson &= bindingMode.equals("auto") || bindingMode.contains("json"); - - // if we do not yet know if its xml or json, then use the binding mode to know the mode - if (!isJson && !isXml) { - isXml = bindingMode.equals("auto") || bindingMode.contains("xml"); - isJson = bindingMode.equals("auto") || bindingMode.contains("json"); - } - - String accept = exchange.getMessage().getHeader("Accept", String.class); - state.put(STATE_KEY_ACCEPT, accept); - - // perform client request validation - if (clientRequestValidation) { - // check if the content-type is accepted according to consumes - if (!isValidOrAcceptedContentType(consumes, contentType)) { - LOG.trace("Consuming content type does not match contentType header {}. Stopping routing.", contentType); - // the content-type is not something we can process so its a HTTP_ERROR 415 - exchange.getMessage().setHeader(Exchange.HTTP_RESPONSE_CODE, 415); - // set empty response body as http error code indicate the problem - exchange.getMessage().setBody(null); - // stop routing and return - exchange.setRouteStop(true); - return; - } - - // check if what is produces is accepted by the client - if (!isValidOrAcceptedContentType(produces, accept)) { - LOG.trace("Produced content type does not match accept header {}. Stopping routing.", contentType); - // the response type is not accepted by the client so its a HTTP_ERROR 406 - exchange.getMessage().setHeader(Exchange.HTTP_RESPONSE_CODE, 406); - // set empty response body as http error code indicate the problem - exchange.getMessage().setBody(null); - // stop routing and return - exchange.setRouteStop(true); - return; - } - } - - String body = null; - if (exchange.getIn().getBody() != null) { - - // okay we have a binding mode, so need to check for empty body as that can cause the marshaller to fail - // as they assume a non-empty body - if (isXml || isJson) { - // we have binding enabled, so we need to know if there body is empty or not - // so force reading the body as a String which we can work with - body = MessageHelper.extractBodyAsString(exchange.getIn()); - if (body != null) { - if (exchange.getIn() instanceof DataTypeAware) { - ((DataTypeAware) exchange.getIn()).setBody(body, new DataType(isJson ? "json" : "xml")); - } else { - exchange.getIn().setBody(body); - } - - if (isXml && isJson) { - // we have still not determined between xml or json, so check the body if its xml based or not - isXml = body.startsWith("<"); - isJson = !isXml; - } - } - } - } - - // add missing default values which are mapped as headers - if (queryDefaultValues != null) { - for (Map.Entry<String, String> entry : queryDefaultValues.entrySet()) { - if (exchange.getIn().getHeader(entry.getKey()) == null) { - exchange.getIn().setHeader(entry.getKey(), entry.getValue()); - } - } - } - - // check for required - if (clientRequestValidation) { - if (requiredBody) { - // the body is required so we need to know if we have a body or not - // so force reading the body as a String which we can work with - if (body == null) { - body = MessageHelper.extractBodyAsString(exchange.getIn()); - if (ObjectHelper.isNotEmpty(body)) { - exchange.getIn().setBody(body); - } - } - if (ObjectHelper.isEmpty(body)) { - // this is a bad request, the client did not include a message body - exchange.getMessage().setHeader(Exchange.HTTP_RESPONSE_CODE, 400); - exchange.getMessage().setBody("The request body is missing."); - // stop routing and return - exchange.setRouteStop(true); - return; - } - } - if (requiredQueryParameters != null - && !exchange.getIn().getHeaders().keySet().containsAll(requiredQueryParameters)) { - // this is a bad request, the client did not include some required query parameters - exchange.getMessage().setHeader(Exchange.HTTP_RESPONSE_CODE, 400); - exchange.getMessage().setBody("Some of the required query parameters are missing."); - // stop routing and return - exchange.setRouteStop(true); - return; - } - if (requiredHeaders != null && !exchange.getIn().getHeaders().keySet().containsAll(requiredHeaders)) { - // this is a bad request, the client did not include some required http headers - exchange.getMessage().setHeader(Exchange.HTTP_RESPONSE_CODE, 400); - exchange.getMessage().setBody("Some of the required HTTP headers are missing."); - // stop routing and return - exchange.setRouteStop(true); - return; - } - } - - // favor json over xml - if (isJson && jsonUnmarshal != null) { - // add reverse operation - state.put(STATE_KEY_DO_MARSHAL, STATE_JSON); - if (ObjectHelper.isNotEmpty(body)) { - try { - jsonUnmarshal.process(exchange); - ExchangeHelper.prepareOutToIn(exchange); - } catch (Exception e) { - exchange.setException(e); - } - if (exchange.isFailed()) { - // we want to indicate that this is a bad request instead of 500 due to parsing error - exchange.getMessage().setHeader(Exchange.HTTP_RESPONSE_CODE, 400); - } - } - if (clientRequestValidation && exchange.isFailed()) { - // this is a bad request, the client included message body that cannot be parsed to json - exchange.getMessage().setHeader(Exchange.HTTP_RESPONSE_CODE, 400); - exchange.getMessage().setBody("Invalid JSon payload."); - // clear exception - exchange.setException(null); - // stop routing and return - exchange.setRouteStop(true); - return; - } - return; - } else if (isXml && xmlUnmarshal != null) { - // add reverse operation - state.put(STATE_KEY_DO_MARSHAL, STATE_XML); - if (ObjectHelper.isNotEmpty(body)) { - try { - xmlUnmarshal.process(exchange); - ExchangeHelper.prepareOutToIn(exchange); - } catch (Exception e) { - exchange.setException(e); - } - if (exchange.isFailed()) { - // we want to indicate that this is a bad request instead of 500 due to parsing error - exchange.getMessage().setHeader(Exchange.HTTP_RESPONSE_CODE, 400); - } - } - if (clientRequestValidation && exchange.isFailed()) { - // this is a bad request, the client included message body that cannot be parsed to XML - exchange.getMessage().setHeader(Exchange.HTTP_RESPONSE_CODE, 400); - exchange.getMessage().setBody("Invalid XML payload."); - // clear exception - exchange.setException(null); - // stop routing and return - exchange.setRouteStop(true); - return; - } - return; - } - - // we could not bind - if ("off".equals(bindingMode) || bindingMode.equals("auto")) { - // okay for auto we do not mind if we could not bind - state.put(STATE_KEY_DO_MARSHAL, STATE_JSON); - } else { - if (bindingMode.contains("xml")) { - exchange.setException( - new CamelExchangeException("Cannot bind to xml as message body is not xml compatible", exchange)); - } else { - exchange.setException( - new CamelExchangeException("Cannot bind to json as message body is not json compatible", exchange)); - } - } - - } - - private void marshal(Exchange exchange, Map<String, Object> state) { - // only marshal if there was no exception - if (exchange.getException() != null) { - return; - } - - if (skipBindingOnErrorCode) { - Integer code = exchange.getMessage().getHeader(Exchange.HTTP_RESPONSE_CODE, Integer.class); - // if there is a custom http error code then skip binding - if (code != null && code >= 300) { - return; - } - } - - boolean isXml = false; - boolean isJson = false; - - // accept takes precedence - String accept = (String) state.get(STATE_KEY_ACCEPT); - if (accept != null) { - isXml = accept.toLowerCase(Locale.ENGLISH).contains("xml"); - isJson = accept.toLowerCase(Locale.ENGLISH).contains("json"); - } - // fallback to content type if still undecided - if (!isXml && !isJson) { - String contentType = ExchangeHelper.getContentType(exchange); - if (contentType != null) { - isXml = contentType.toLowerCase(Locale.ENGLISH).contains("xml"); - isJson = contentType.toLowerCase(Locale.ENGLISH).contains("json"); - } - } - // if content type could not tell us if it was json or xml, then fallback to if the binding was configured with - // that information in the consumes - if (!isXml && !isJson) { - isXml = produces != null && produces.toLowerCase(Locale.ENGLISH).contains("xml"); - isJson = produces != null && produces.toLowerCase(Locale.ENGLISH).contains("json"); - } - - // only allow xml/json if the binding mode allows that (when off we still want to know if its xml or json) - if (bindingMode != null) { - isXml &= bindingMode.equals("off") || bindingMode.equals("auto") || bindingMode.contains("xml"); - isJson &= bindingMode.equals("off") || bindingMode.equals("auto") || bindingMode.contains("json"); - - // if we do not yet know if its xml or json, then use the binding mode to know the mode - if (!isJson && !isXml) { - isXml = bindingMode.equals("auto") || bindingMode.contains("xml"); - isJson = bindingMode.equals("auto") || bindingMode.contains("json"); - } - } - - // in case we have not yet been able to determine if xml or json, then use the same as in the unmarshaller - if (isXml && isJson) { - isXml = state.get(STATE_KEY_DO_MARSHAL).equals(STATE_XML); - isJson = !isXml; - } - - // need to prepare exchange first - ExchangeHelper.prepareOutToIn(exchange); - - // ensure there is a content type header (even if binding is off) - ensureHeaderContentType(produces, isXml, isJson, exchange); - - if (bindingMode == null || "off".equals(bindingMode)) { - // binding is off, so no message body binding - return; - } - - // is there any marshaller at all - if (jsonMarshal == null && xmlMarshal == null) { - return; - } - - // is the body empty - if (exchange.getMessage().getBody() == null) { - return; - } - - String contentType = exchange.getIn().getHeader(Exchange.CONTENT_TYPE, String.class); - // need to lower-case so the contains check below can match if using upper case - contentType = contentType.toLowerCase(Locale.US); - try { - // favor json over xml - if (isJson && jsonMarshal != null) { - // only marshal if its json content type - if (contentType.contains("json")) { - jsonMarshal.process(exchange); - setOutputDataType(exchange, new DataType("json")); - - if (enableNoContentResponse) { - String body = MessageHelper.extractBodyAsString(exchange.getMessage()); - if (ObjectHelper.isNotEmpty(body) && (body.equals("[]") || body.equals("{}"))) { - exchange.getMessage().setHeader(Exchange.HTTP_RESPONSE_CODE, 204); - exchange.getMessage().setBody(""); - } - } - } - } else if (isXml && xmlMarshal != null) { - // only marshal if its xml content type - if (contentType.contains("xml")) { - xmlMarshal.process(exchange); - setOutputDataType(exchange, new DataType("xml")); - - if (enableNoContentResponse) { - String body = MessageHelper.extractBodyAsString(exchange.getMessage()).replace("\n", ""); - if (ObjectHelper.isNotEmpty(body)) { - int open = 0; - int close = body.indexOf('>'); - // xml declaration - if (body.startsWith("<?xml")) { - open = close; - close = body.indexOf('>', close + 1); - } - // empty root element <el/> or <el></el> - if (body.length() == close + 1 || body.length() == (open + 1 + 2 * (close - open) + 1)) { - exchange.getMessage().setHeader(Exchange.HTTP_RESPONSE_CODE, 204); - exchange.getMessage().setBody(""); - } - } - } - } - } else { - // we could not bind - if (bindingMode.equals("auto")) { - // okay for auto we do not mind if we could not bind - } else { - if (bindingMode.contains("xml")) { - exchange.setException(new CamelExchangeException( - "Cannot bind to xml as message body is not xml compatible", exchange)); - } else { - exchange.setException(new CamelExchangeException( - "Cannot bind to json as message body is not json compatible", exchange)); - } - } - } - } catch (Exception e) { - exchange.setException(e); - } - } - - private void setOutputDataType(Exchange exchange, DataType type) { - Message target = exchange.getMessage(); - if (target instanceof DataTypeAware) { - ((DataTypeAware) target).setDataType(type); - } - } - - private void ensureHeaderContentType(String contentType, boolean isXml, boolean isJson, Exchange exchange) { - // favor given content type - if (contentType != null) { - String type = ExchangeHelper.getContentType(exchange); - if (type == null) { - exchange.getIn().setHeader(Exchange.CONTENT_TYPE, contentType); - } - } - - // favor json over xml - if (isJson) { - // make sure there is a content-type with json - String type = ExchangeHelper.getContentType(exchange); - if (type == null) { - exchange.getIn().setHeader(Exchange.CONTENT_TYPE, "application/json"); - } - } else if (isXml) { - // make sure there is a content-type with xml - String type = ExchangeHelper.getContentType(exchange); - if (type == null) { - exchange.getIn().setHeader(Exchange.CONTENT_TYPE, "application/xml"); - } - } - } - - private void setCORSHeaders(Exchange exchange) { - // add the CORS headers after routing, but before the consumer writes the response - Message msg = exchange.getMessage(); - - // use default value if none has been configured - String allowOrigin = corsHeaders != null ? corsHeaders.get("Access-Control-Allow-Origin") : null; - if (allowOrigin == null) { - allowOrigin = RestConfiguration.CORS_ACCESS_CONTROL_ALLOW_ORIGIN; - } - String allowMethods = corsHeaders != null ? corsHeaders.get("Access-Control-Allow-Methods") : null; - if (allowMethods == null) { - allowMethods = RestConfiguration.CORS_ACCESS_CONTROL_ALLOW_METHODS; - } - String allowHeaders = corsHeaders != null ? corsHeaders.get("Access-Control-Allow-Headers") : null; - if (allowHeaders == null) { - allowHeaders = RestConfiguration.CORS_ACCESS_CONTROL_ALLOW_HEADERS; - } - String maxAge = corsHeaders != null ? corsHeaders.get("Access-Control-Max-Age") : null; - if (maxAge == null) { - maxAge = RestConfiguration.CORS_ACCESS_CONTROL_MAX_AGE; - } - String allowCredentials = corsHeaders != null ? corsHeaders.get("Access-Control-Allow-Credentials") : null; - - // Restrict the origin if credentials are allowed. - // https://www.w3.org/TR/cors/ - section 6.1, point 3 - String origin = exchange.getIn().getHeader("Origin", String.class); - if ("true".equalsIgnoreCase(allowCredentials) && "*".equals(allowOrigin) && origin != null) { - allowOrigin = origin; - } - - msg.setHeader("Access-Control-Allow-Origin", allowOrigin); - msg.setHeader("Access-Control-Allow-Methods", allowMethods); - msg.setHeader("Access-Control-Allow-Headers", allowHeaders); - msg.setHeader("Access-Control-Max-Age", maxAge); - if (allowCredentials != null) { - msg.setHeader("Access-Control-Allow-Credentials", allowCredentials); - } + super(camelContext, jsonDataFormat, xmlDataFormat, outJsonDataFormat, outXmlDataFormat, + consumes, produces, bindingMode, skipBindingOnErrorCode, clientRequestValidation, + enableCORS, enableNoContentResponse, corsHeaders, queryDefaultValues, + requiredBody, requiredQueryParameters, requiredHeaders); } } diff --git a/core/camel-core-reifier/src/main/java/org/apache/camel/reifier/rest/RestBindingReifier.java b/core/camel-core-reifier/src/main/java/org/apache/camel/reifier/rest/RestBindingReifier.java index ccc54d21254..20b9cf309c1 100644 --- a/core/camel-core-reifier/src/main/java/org/apache/camel/reifier/rest/RestBindingReifier.java +++ b/core/camel-core-reifier/src/main/java/org/apache/camel/reifier/rest/RestBindingReifier.java @@ -41,6 +41,8 @@ public class RestBindingReifier extends AbstractReifier { } public RestBindingAdvice createRestBindingAdvice() throws Exception { + // TODO: Use RestBindingFactory + RestConfiguration config = CamelContextHelper.getRestConfiguration(camelContext, definition.getComponent()); // these options can be overridden per rest verb @@ -64,6 +66,8 @@ public class RestBindingReifier extends AbstractReifier { if (definition.getClientRequestValidation() != null) { validation = parseBoolean(definition.getClientRequestValidation(), false); } + String consumes = parseString(definition.getConsumes()); + String produces = parseString(definition.getProduces()); // cors headers Map<String, String> corsHeaders = config.getCorsHeaders(); @@ -72,7 +76,7 @@ public class RestBindingReifier extends AbstractReifier { // binding mode is off, so create off mode binding processor return new RestBindingAdvice( camelContext, null, null, null, null, - parseString(definition.getConsumes()), parseString(definition.getProduces()), mode, skip, validation, cors, + consumes, produces, mode, skip, validation, cors, noContentResponse, corsHeaders, definition.getDefaultValues(), definition.getRequiredBody() != null ? definition.getRequiredBody() : false, definition.getRequiredQueryParameters(), definition.getRequiredHeaders()); @@ -150,6 +154,7 @@ public class RestBindingReifier extends AbstractReifier { definition.getRequiredQueryParameters(), definition.getRequiredHeaders()); } + @Deprecated protected void setupJson( RestConfiguration config, String type, Class<?> typeClass, String outType, Class<?> outTypeClass, DataFormat json, DataFormat outJson) @@ -197,6 +202,7 @@ public class RestBindingReifier extends AbstractReifier { setAdditionalConfiguration(config, outJson, "json.out."); } + @Deprecated private void setAdditionalConfiguration(RestConfiguration config, DataFormat dataFormat, String prefix) { if (config.getDataFormatProperties() != null && !config.getDataFormatProperties().isEmpty()) { // must use a copy as otherwise the options gets removed during @@ -226,6 +232,7 @@ public class RestBindingReifier extends AbstractReifier { } } + @Deprecated private boolean isKeyKnownPrefix(String key) { return key.startsWith("json.in.") || key.startsWith("json.out.") || key.startsWith("xml.in.") || key.startsWith("xml.out."); diff --git a/core/camel-support/src/main/java/org/apache/camel/support/processor/RestBindingConfiguration.java b/core/camel-support/src/main/java/org/apache/camel/support/processor/RestBindingConfiguration.java new file mode 100644 index 00000000000..2d54a15ca42 --- /dev/null +++ b/core/camel-support/src/main/java/org/apache/camel/support/processor/RestBindingConfiguration.java @@ -0,0 +1,171 @@ +/* + * 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.support.processor; + +import java.util.Map; +import java.util.Set; + +/** + * Configuration for {@link RestBindingSupport}. + */ +public class RestBindingConfiguration { + + private String consumes; + private String produces; + private String bindingMode; + private boolean skipBindingOnErrorCode; + private boolean clientRequestValidation; + private boolean enableCORS; + private boolean enableNoContentResponse; + private Map<String, String> corsHeaders; + private Map<String, String> queryDefaultValues; + private boolean requiredBody; + private Set<String> requiredQueryParameters; + private Set<String> requiredHeaders; + private String type; + private Class<?> typeClass; + private String outType; + private Class<?> outTypeClass; + + public String getConsumes() { + return consumes; + } + + public void setConsumes(String consumes) { + this.consumes = consumes; + } + + public String getProduces() { + return produces; + } + + public void setProduces(String produces) { + this.produces = produces; + } + + public String getBindingMode() { + return bindingMode; + } + + public void setBindingMode(String bindingMode) { + this.bindingMode = bindingMode; + } + + public boolean isSkipBindingOnErrorCode() { + return skipBindingOnErrorCode; + } + + public void setSkipBindingOnErrorCode(boolean skipBindingOnErrorCode) { + this.skipBindingOnErrorCode = skipBindingOnErrorCode; + } + + public boolean isClientRequestValidation() { + return clientRequestValidation; + } + + public void setClientRequestValidation(boolean clientRequestValidation) { + this.clientRequestValidation = clientRequestValidation; + } + + public boolean isEnableCORS() { + return enableCORS; + } + + public void setEnableCORS(boolean enableCORS) { + this.enableCORS = enableCORS; + } + + public boolean isEnableNoContentResponse() { + return enableNoContentResponse; + } + + public void setEnableNoContentResponse(boolean enableNoContentResponse) { + this.enableNoContentResponse = enableNoContentResponse; + } + + public Map<String, String> getCorsHeaders() { + return corsHeaders; + } + + public void setCorsHeaders(Map<String, String> corsHeaders) { + this.corsHeaders = corsHeaders; + } + + public Map<String, String> getQueryDefaultValues() { + return queryDefaultValues; + } + + public void setQueryDefaultValues(Map<String, String> queryDefaultValues) { + this.queryDefaultValues = queryDefaultValues; + } + + public boolean isRequiredBody() { + return requiredBody; + } + + public void setRequiredBody(boolean requiredBody) { + this.requiredBody = requiredBody; + } + + public Set<String> getRequiredQueryParameters() { + return requiredQueryParameters; + } + + public void setRequiredQueryParameters(Set<String> requiredQueryParameters) { + this.requiredQueryParameters = requiredQueryParameters; + } + + public Set<String> getRequiredHeaders() { + return requiredHeaders; + } + + public void setRequiredHeaders(Set<String> requiredHeaders) { + this.requiredHeaders = requiredHeaders; + } + + public String getType() { + return type; + } + + public void setType(String type) { + this.type = type; + } + + public Class<?> getTypeClass() { + return typeClass; + } + + public void setTypeClass(Class<?> typeClass) { + this.typeClass = typeClass; + } + + public String getOutType() { + return outType; + } + + public void setOutType(String outType) { + this.outType = outType; + } + + public Class<?> getOutTypeClass() { + return outTypeClass; + } + + public void setOutTypeClass(Class<?> outTypeClass) { + this.outTypeClass = outTypeClass; + } +} diff --git a/core/camel-core-reifier/src/main/java/org/apache/camel/reifier/rest/RestBindingReifier.java b/core/camel-support/src/main/java/org/apache/camel/support/processor/RestBindingFactory.java similarity index 64% copy from core/camel-core-reifier/src/main/java/org/apache/camel/reifier/rest/RestBindingReifier.java copy to core/camel-support/src/main/java/org/apache/camel/support/processor/RestBindingFactory.java index ccc54d21254..b1d9072d05f 100644 --- a/core/camel-core-reifier/src/main/java/org/apache/camel/reifier/rest/RestBindingReifier.java +++ b/core/camel-support/src/main/java/org/apache/camel/support/processor/RestBindingFactory.java @@ -14,68 +14,36 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.apache.camel.reifier.rest; +package org.apache.camel.support.processor; import java.util.HashMap; import java.util.Map; -import org.apache.camel.Route; -import org.apache.camel.model.rest.RestBindingDefinition; -import org.apache.camel.model.rest.RestBindingMode; -import org.apache.camel.processor.RestBindingAdvice; -import org.apache.camel.reifier.AbstractReifier; +import org.apache.camel.CamelContext; import org.apache.camel.spi.BeanIntrospection; import org.apache.camel.spi.DataFormat; import org.apache.camel.spi.RestConfiguration; -import org.apache.camel.support.CamelContextHelper; +import org.apache.camel.support.EndpointHelper; import org.apache.camel.support.PluginHelper; import org.apache.camel.support.PropertyBindingSupport; -public class RestBindingReifier extends AbstractReifier { - - private final RestBindingDefinition definition; - - public RestBindingReifier(Route route, RestBindingDefinition definition) { - super(route); - this.definition = definition; - } - - public RestBindingAdvice createRestBindingAdvice() throws Exception { - RestConfiguration config = CamelContextHelper.getRestConfiguration(camelContext, definition.getComponent()); +/** + * Factory to create {@link RestBindingSupport} from the given configuration. + */ +public class RestBindingFactory { - // these options can be overridden per rest verb + public static RestBindingSupport build(CamelContext camelContext, RestBindingConfiguration bc) throws Exception { + RestConfiguration config = camelContext.getRestConfiguration(); String mode = config.getBindingMode().name(); - if (definition.getBindingMode() != null) { - mode = parse(RestBindingMode.class, definition.getBindingMode()).name(); - } - boolean cors = config.isEnableCORS(); - if (definition.getEnableCORS() != null) { - cors = parseBoolean(definition.getEnableCORS(), false); - } - boolean noContentResponse = config.isEnableNoContentResponse(); - if (definition.getEnableNoContentResponse() != null) { - noContentResponse = parseBoolean(definition.getEnableNoContentResponse(), false); - } - boolean skip = config.isSkipBindingOnErrorCode(); - if (definition.getSkipBindingOnErrorCode() != null) { - skip = parseBoolean(definition.getSkipBindingOnErrorCode(), false); - } - boolean validation = config.isClientRequestValidation(); - if (definition.getClientRequestValidation() != null) { - validation = parseBoolean(definition.getClientRequestValidation(), false); - } - - // cors headers - Map<String, String> corsHeaders = config.getCorsHeaders(); if ("off".equals(mode)) { // binding mode is off, so create off mode binding processor - return new RestBindingAdvice( + return new RestBindingSupport( camelContext, null, null, null, null, - parseString(definition.getConsumes()), parseString(definition.getProduces()), mode, skip, validation, cors, - noContentResponse, corsHeaders, - definition.getDefaultValues(), definition.getRequiredBody() != null ? definition.getRequiredBody() : false, - definition.getRequiredQueryParameters(), definition.getRequiredHeaders()); + bc.getConsumes(), bc.getProduces(), mode, bc.isSkipBindingOnErrorCode(), bc.isClientRequestValidation(), bc.isEnableCORS(), + bc.isEnableNoContentResponse(), bc.getCorsHeaders(), + bc.getQueryDefaultValues(), bc.isRequiredBody(), bc.getRequiredQueryParameters(), + bc.getRequiredHeaders()); } // setup json data format @@ -85,7 +53,7 @@ public class RestBindingReifier extends AbstractReifier { String name = config.getJsonDataFormat(); if (name != null) { // must only be a name, not refer to an existing instance - Object instance = lookupByName(name); + Object instance = lookupByName(camelContext, name); if (instance != null) { throw new IllegalArgumentException( "JsonDataFormat name: " + name + " must not be an existing bean instance from the registry"); @@ -99,10 +67,9 @@ public class RestBindingReifier extends AbstractReifier { outJson = camelContext.createDataFormat(name); if (json != null) { - setupJson( - config, - parseString(definition.getType()), definition.getTypeClass(), - parseString(definition.getOutType()), definition.getOutTypeClass(), + setupJson(camelContext, config, + bc.getType(), bc.getTypeClass(), + bc.getOutType(), bc.getOutTypeClass(), json, outJson); } } @@ -114,7 +81,7 @@ public class RestBindingReifier extends AbstractReifier { String name = config.getXmlDataFormat(); if (name != null) { // must only be a name, not refer to an existing instance - Object instance = lookupByName(name); + Object instance = lookupByName(camelContext, name); if (instance != null) { throw new IllegalArgumentException( "XmlDataFormat name: " + name + " must not be an existing bean instance from the registry"); @@ -134,23 +101,23 @@ public class RestBindingReifier extends AbstractReifier { if (jaxb != null) { // to setup JAXB we need to use camel-jaxb - PluginHelper.getRestBindingJaxbDataFormatFactory(camelContext).setupJaxb( - camelContext, config, - parseString(definition.getType()), definition.getTypeClass(), - parseString(definition.getOutType()), definition.getOutTypeClass(), - jaxb, outJaxb); + PluginHelper.getRestBindingJaxbDataFormatFactory(camelContext). + setupJaxb(camelContext, config, + bc.getType(), bc.getTypeClass(), + bc.getOutType(), bc.getOutTypeClass(), + jaxb, outJaxb); } } - return new RestBindingAdvice( + return new RestBindingSupport( camelContext, json, jaxb, outJson, outJaxb, - parseString(definition.getConsumes()), parseString(definition.getProduces()), - mode, skip, validation, cors, noContentResponse, corsHeaders, - definition.getDefaultValues(), definition.getRequiredBody() != null ? definition.getRequiredBody() : false, - definition.getRequiredQueryParameters(), definition.getRequiredHeaders()); + bc.getConsumes(), bc.getProduces(), mode, bc.isSkipBindingOnErrorCode(), bc.isClientRequestValidation(), bc.isEnableCORS(), + bc.isEnableNoContentResponse(), bc.getCorsHeaders(), + bc.getQueryDefaultValues(), bc.isRequiredBody(), bc.getRequiredQueryParameters(), + bc.getRequiredHeaders()); } - protected void setupJson( + protected static void setupJson(CamelContext camelContext, RestConfiguration config, String type, Class<?> typeClass, String outType, Class<?> outTypeClass, DataFormat json, DataFormat outJson) throws Exception { @@ -173,7 +140,7 @@ public class RestBindingReifier extends AbstractReifier { useList); } - setAdditionalConfiguration(config, json, "json.in."); + setAdditionalConfiguration(camelContext, config, json, "json.in."); Class<?> outClazz = null; boolean outUseList = false; @@ -194,10 +161,10 @@ public class RestBindingReifier extends AbstractReifier { outUseList); } - setAdditionalConfiguration(config, outJson, "json.out."); + setAdditionalConfiguration(camelContext, config, outJson, "json.out."); } - private void setAdditionalConfiguration(RestConfiguration config, DataFormat dataFormat, String prefix) { + private static void setAdditionalConfiguration(CamelContext camelContext, RestConfiguration config, DataFormat dataFormat, String prefix) { if (config.getDataFormatProperties() != null && !config.getDataFormatProperties().isEmpty()) { // must use a copy as otherwise the options gets removed during // introspection setProperties @@ -226,9 +193,21 @@ public class RestBindingReifier extends AbstractReifier { } } - private boolean isKeyKnownPrefix(String key) { + private static boolean isKeyKnownPrefix(String key) { return key.startsWith("json.in.") || key.startsWith("json.out.") || key.startsWith("xml.in.") - || key.startsWith("xml.out."); + || key.startsWith("xml.out."); + } + + private static Object lookupByName(CamelContext camelContext, String name) { + if (name == null) { + return null; + } + + if (EndpointHelper.isReferenceParameter(name)) { + return EndpointHelper.resolveReferenceParameter(camelContext, name, Object.class, false); + } else { + return camelContext.getRegistry().lookupByName(name); + } } } diff --git a/core/camel-core-processor/src/main/java/org/apache/camel/processor/RestBindingAdvice.java b/core/camel-support/src/main/java/org/apache/camel/support/processor/RestBindingSupport.java similarity index 96% copy from core/camel-core-processor/src/main/java/org/apache/camel/processor/RestBindingAdvice.java copy to core/camel-support/src/main/java/org/apache/camel/support/processor/RestBindingSupport.java index 0efcf7c0dae..f7ca71d7515 100644 --- a/core/camel-core-processor/src/main/java/org/apache/camel/processor/RestBindingAdvice.java +++ b/core/camel-support/src/main/java/org/apache/camel/support/processor/RestBindingSupport.java @@ -14,12 +14,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.apache.camel.processor; - -import java.util.HashMap; -import java.util.Locale; -import java.util.Map; -import java.util.Set; +package org.apache.camel.support.processor; import org.apache.camel.AsyncProcessor; import org.apache.camel.CamelContext; @@ -33,26 +28,27 @@ import org.apache.camel.spi.DataTypeAware; import org.apache.camel.spi.RestConfiguration; import org.apache.camel.support.ExchangeHelper; import org.apache.camel.support.MessageHelper; -import org.apache.camel.support.processor.MarshalProcessor; -import org.apache.camel.support.processor.UnmarshalProcessor; +import org.apache.camel.support.service.ServiceHelper; +import org.apache.camel.support.service.ServiceSupport; import org.apache.camel.util.ObjectHelper; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import java.util.HashMap; +import java.util.Locale; +import java.util.Map; +import java.util.Set; + import static org.apache.camel.support.http.RestUtil.isValidOrAcceptedContentType; /** - * A {@link CamelInternalProcessorAdvice} that binds the REST DSL incoming and outgoing messages from sources of json or - * xml to Java Objects. - * <p/> - * The binding uses {@link org.apache.camel.spi.DataFormat} for the actual work to transform from xml/json to Java - * Objects and reverse again. - * <p/> - * The rest producer side is implemented in {@link org.apache.camel.component.rest.RestProducerBindingProcessor} + * Used for Rest DSL with binding to json/xml for incoming requests and outgoing responses. + * + * @see RestBindingFactory */ -public class RestBindingAdvice implements CamelInternalProcessorAdvice<Map<String, Object>> { +public class RestBindingSupport extends ServiceSupport implements CamelInternalProcessorAdvice<Map<String, Object>> { - private static final Logger LOG = LoggerFactory.getLogger(RestBindingAdvice.class); + private static final Logger LOG = LoggerFactory.getLogger(RestBindingSupport.class); private static final String STATE_KEY_DO_MARSHAL = "doMarshal"; private static final String STATE_KEY_ACCEPT = "accept"; private static final String STATE_JSON = "json"; @@ -75,7 +71,10 @@ public class RestBindingAdvice implements CamelInternalProcessorAdvice<Map<Strin private final Set<String> requiredQueryParameters; private final Set<String> requiredHeaders; - public RestBindingAdvice(CamelContext camelContext, DataFormat jsonDataFormat, DataFormat xmlDataFormat, + /** + * Use {@link RestBindingFactory} to create. + */ + public RestBindingSupport(CamelContext camelContext, DataFormat jsonDataFormat, DataFormat xmlDataFormat, DataFormat outJsonDataFormat, DataFormat outXmlDataFormat, String consumes, String produces, String bindingMode, boolean skipBindingOnErrorCode, boolean clientRequestValidation, boolean enableCORS, @@ -286,7 +285,7 @@ public class RestBindingAdvice implements CamelInternalProcessorAdvice<Map<Strin } } if (requiredQueryParameters != null - && !exchange.getIn().getHeaders().keySet().containsAll(requiredQueryParameters)) { + && !exchange.getIn().getHeaders().keySet().containsAll(requiredQueryParameters)) { // this is a bad request, the client did not include some required query parameters exchange.getMessage().setHeader(Exchange.HTTP_RESPONSE_CODE, 400); exchange.getMessage().setBody("Some of the required query parameters are missing."); @@ -585,4 +584,13 @@ public class RestBindingAdvice implements CamelInternalProcessorAdvice<Map<Strin } } + @Override + protected void doStart() throws Exception { + ServiceHelper.startService(jsonUnmarshal, xmlUnmarshal, jsonMarshal, xmlMarshal); + } + + @Override + protected void doStop() throws Exception { + ServiceHelper.stopService(jsonUnmarshal, xmlUnmarshal, jsonMarshal, xmlMarshal); + } }