This is an automated email from the ASF dual-hosted git repository. lburgazzoli pushed a commit to branch master in repository https://gitbox.apache.org/repos/asf/camel-quarkus.git
commit c3ef475aa92c3acb26d16aa9f77ace9e2788c063 Author: lburgazzoli <[email protected]> AuthorDate: Thu Apr 2 10:56:15 2020 +0200 Workaround for RestProducer on Java 11 --- .../component/rest/deployment/RestProcessor.java | 72 ++++++- extensions/rest/runtime/pom.xml | 5 + .../component/rest/QuarkusRestComponent.java | 194 +++++++++++++++++++ .../component/rest/QuarkusRestEndpoint.java | 204 +++++++++++++++++++ .../component/rest/QuarkusRestProducer.java | 215 +++++++++++++++++++++ .../quarkus/component/rest/RestRecorder.java} | 18 +- .../component/rest/graal/NoJAXBContext.java} | 21 +- .../rest/graal/RestProducerSubstitution.java} | 21 +- 8 files changed, 717 insertions(+), 33 deletions(-) diff --git a/extensions/rest/deployment/src/main/java/org/apache/camel/quarkus/component/rest/deployment/RestProcessor.java b/extensions/rest/deployment/src/main/java/org/apache/camel/quarkus/component/rest/deployment/RestProcessor.java index daa2fdb..ffed329 100644 --- a/extensions/rest/deployment/src/main/java/org/apache/camel/quarkus/component/rest/deployment/RestProcessor.java +++ b/extensions/rest/deployment/src/main/java/org/apache/camel/quarkus/component/rest/deployment/RestProcessor.java @@ -16,11 +16,21 @@ */ package org.apache.camel.quarkus.component.rest.deployment; +import io.quarkus.deployment.Capabilities; +import io.quarkus.deployment.annotations.BuildProducer; import io.quarkus.deployment.annotations.BuildStep; +import io.quarkus.deployment.annotations.ExecutionTime; +import io.quarkus.deployment.annotations.Record; import io.quarkus.deployment.builditem.FeatureBuildItem; +import org.apache.camel.component.rest.RestComponent; +import org.apache.camel.quarkus.component.rest.RestRecorder; +import org.apache.camel.quarkus.component.rest.graal.NoJAXBContext; +import org.apache.camel.quarkus.core.deployment.CamelBeanBuildItem; +import org.apache.camel.quarkus.core.deployment.CamelServiceFilter; +import org.apache.camel.quarkus.core.deployment.CamelServiceFilterBuildItem; +import org.apache.camel.quarkus.support.common.CamelCapabilities; class RestProcessor { - private static final String FEATURE = "camel-rest"; @BuildStep @@ -28,4 +38,64 @@ class RestProcessor { return new FeatureBuildItem(FEATURE); } + // + // RestAssured brings XML bind APIs to the classpath: + // + // [INFO] +- io.rest-assured:rest-assured:jar:4.3.0:test + // [INFO] | +- org.codehaus.groovy:groovy:jar:3.0.2:test + // [INFO] | +- org.codehaus.groovy:groovy-xml:jar:3.0.2:test + // [INFO] | +- org.apache.httpcomponents:httpclient:jar:4.5.11:test + // [INFO] | | +- org.apache.httpcomponents:httpcore:jar:4.4.13:test + // [INFO] | | \- commons-codec:commons-codec:jar:1.13:test + // [INFO] | +- org.apache.httpcomponents:httpmime:jar:4.5.3:test + // [INFO] | +- org.hamcrest:hamcrest:jar:2.1:test + // [INFO] | +- org.ccil.cowan.tagsoup:tagsoup:jar:1.2.1:test + // [INFO] | +- io.rest-assured:json-path:jar:4.3.0:test + // [INFO] | | +- org.codehaus.groovy:groovy-json:jar:3.0.2:test + // [INFO] | | \- io.rest-assured:rest-assured-common:jar:4.3.0:test + // >> [INFO] | \- io.rest-assured:xml-path:jar:4.3.0:test + // [INFO] | +- org.apache.commons:commons-lang3:jar:3.9:test + // >> [INFO] | +- jakarta.xml.bind:jakarta.xml.bind-api:jar:2.3.2:test + // [INFO] | | \- jakarta.activation:jakarta.activation-api:jar:1.2.1:test + // [INFO] | \- org.apache.sling:org.apache.sling.javax.activation:jar:0.1.0:test + // + // For tests in JVM mode the condition NoJAXBContext is always false as a consequence of + // RestAssured transitive dependencies so we need an additional check on the presence of + // the org.apache.camel.xml.jaxb feature to make the behaviour consistent ion both modes. + // + // Excluding io.rest-assured:xml-path from the transitive dependencies does not seem to work + // as it lead to the RestAssured framework to fail to instantiate. + // + + @BuildStep(onlyIf = NoJAXBContext.class) + void serviceFilter( + Capabilities capabilities, + BuildProducer<CamelServiceFilterBuildItem> serviceFilter) { + + // if jaxb is configured, don't replace the method + if (capabilities.isCapabilityPresent(CamelCapabilities.XML_JAXB)) { + return; + } + + serviceFilter.produce(new CamelServiceFilterBuildItem(CamelServiceFilter.forComponent("rest"))); + } + + @Record(ExecutionTime.STATIC_INIT) + @BuildStep(onlyIf = NoJAXBContext.class) + void restComponent( + RestRecorder recorder, + Capabilities capabilities, + BuildProducer<CamelBeanBuildItem> camelBeans) { + + // if jaxb is configured, don't replace the method + if (capabilities.isCapabilityPresent(CamelCapabilities.XML_JAXB)) { + return; + } + + camelBeans.produce( + new CamelBeanBuildItem( + "rest", + RestComponent.class.getName(), + recorder.createRestComponent())); + } } diff --git a/extensions/rest/runtime/pom.xml b/extensions/rest/runtime/pom.xml index 24f85f8..68e3328 100644 --- a/extensions/rest/runtime/pom.xml +++ b/extensions/rest/runtime/pom.xml @@ -56,6 +56,11 @@ <groupId>org.apache.camel</groupId> <artifactId>camel-rest</artifactId> </dependency> + + <dependency> + <groupId>org.graalvm.nativeimage</groupId> + <artifactId>svm</artifactId> + </dependency> </dependencies> <build> diff --git a/extensions/rest/runtime/src/main/java/org/apache/camel/quarkus/component/rest/QuarkusRestComponent.java b/extensions/rest/runtime/src/main/java/org/apache/camel/quarkus/component/rest/QuarkusRestComponent.java new file mode 100644 index 0000000..13925b9 --- /dev/null +++ b/extensions/rest/runtime/src/main/java/org/apache/camel/quarkus/component/rest/QuarkusRestComponent.java @@ -0,0 +1,194 @@ +/* + * 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.quarkus.component.rest; + +import java.util.HashMap; +import java.util.LinkedHashMap; +import java.util.Map; + +import org.apache.camel.CamelContext; +import org.apache.camel.Endpoint; +import org.apache.camel.component.rest.RestComponent; +import org.apache.camel.spi.RestConfiguration; +import org.apache.camel.support.CamelContextHelper; +import org.apache.camel.util.FileUtil; +import org.apache.camel.util.StringHelper; +import org.apache.camel.util.URISupport; + +public class QuarkusRestComponent extends RestComponent { + @Override + protected Endpoint createEndpoint(String uri, String remaining, Map<String, Object> parameters) throws Exception { + String cname = getAndRemoveParameter(parameters, "consumerComponentName", String.class, getConsumerComponentName()); + String pname = getAndRemoveParameter(parameters, "producerComponentName", String.class, getProducerComponentName()); + + QuarkusRestEndpoint answer = new QuarkusRestEndpoint(uri, this); + answer.setConsumerComponentName(cname); + answer.setProducerComponentName(pname); + answer.setApiDoc(getApiDoc()); + + RestConfiguration config = new RestConfiguration(); + mergeConfigurations(getCamelContext(), config, findGlobalRestConfiguration()); + mergeConfigurations(getCamelContext(), config, getCamelContext().getRestConfiguration(cname, false)); + mergeConfigurations(getCamelContext(), config, getCamelContext().getRestConfiguration(pname, false)); + + // if no explicit host was given, then fallback and use default configured host + String h = getAndRemoveOrResolveReferenceParameter(parameters, "host", String.class, getHost()); + if (h == null) { + h = config.getHost(); + int port = config.getPort(); + // is there a custom port number + if (port > 0 && port != 80 && port != 443) { + h += ":" + port; + } + } + // host must start with http:// or https:// + if (h != null && !(h.startsWith("http://") || h.startsWith("https://"))) { + h = "http://" + h; + } + answer.setHost(h); + + setProperties(answer, parameters); + if (!parameters.isEmpty()) { + // use only what remains and at this point parameters that have been used have been removed + // without overwriting any query parameters set via queryParameters endpoint option + final Map<String, Object> queryParameters = new LinkedHashMap<>(parameters); + final Map<String, Object> existingQueryParameters = URISupport.parseQuery(answer.getQueryParameters()); + queryParameters.putAll(existingQueryParameters); + + final String remainingParameters = URISupport.createQueryString(queryParameters); + answer.setQueryParameters(remainingParameters); + } + + answer.setParameters(parameters); + + if (!remaining.contains(":")) { + throw new IllegalArgumentException( + "Invalid syntax. Must be rest:method:path[:uriTemplate] where uriTemplate is optional"); + } + + String method = StringHelper.before(remaining, ":"); + String s = StringHelper.after(remaining, ":"); + + String path; + String uriTemplate; + if (s != null && s.contains(":")) { + path = StringHelper.before(s, ":"); + uriTemplate = StringHelper.after(s, ":"); + } else { + path = s; + uriTemplate = null; + } + + // remove trailing slashes + path = FileUtil.stripTrailingSeparator(path); + uriTemplate = FileUtil.stripTrailingSeparator(uriTemplate); + + answer.setMethod(method); + answer.setPath(path); + answer.setUriTemplate(uriTemplate); + + // if no explicit component name was given, then fallback and use default configured component name + if (answer.getProducerComponentName() == null) { + String name = config.getProducerComponent(); + answer.setProducerComponentName(name); + } + if (answer.getConsumerComponentName() == null) { + String name = config.getComponent(); + answer.setConsumerComponentName(name); + } + // if no explicit producer api was given, then fallback and use default configured + if (answer.getApiDoc() == null) { + answer.setApiDoc(config.getProducerApiDoc()); + } + + return answer; + } + + private RestConfiguration findGlobalRestConfiguration() { + CamelContext context = getCamelContext(); + + RestConfiguration conf = CamelContextHelper.lookup(context, DEFAULT_REST_CONFIGURATION_ID, RestConfiguration.class); + if (conf == null) { + conf = CamelContextHelper.findByType(getCamelContext(), RestConfiguration.class); + } + + return conf; + } + + private RestConfiguration mergeConfigurations(CamelContext camelContext, RestConfiguration conf, RestConfiguration from) + throws Exception { + if (conf == from) { + return conf; + } + if (from != null) { + // Merge properties + conf.setComponent(or(conf.getComponent(), from.getComponent())); + conf.setApiComponent(or(conf.getApiComponent(), from.getApiComponent())); + conf.setProducerComponent(or(conf.getProducerComponent(), from.getProducerComponent())); + conf.setProducerApiDoc(or(conf.getProducerApiDoc(), from.getProducerApiDoc())); + conf.setScheme(or(conf.getScheme(), from.getScheme())); + conf.setHost(or(conf.getHost(), from.getHost())); + conf.setUseXForwardHeaders(or(conf.isUseXForwardHeaders(), from.isUseXForwardHeaders())); + conf.setApiHost(or(conf.getApiHost(), from.getApiHost())); + conf.setPort(or(conf.getPort(), from.getPort())); + conf.setContextPath(or(conf.getContextPath(), from.getContextPath())); + conf.setApiContextPath(or(conf.getApiContextPath(), from.getApiContextPath())); + conf.setApiContextRouteId(or(conf.getApiContextRouteId(), from.getApiContextRouteId())); + conf.setApiContextIdPattern(or(conf.getApiContextIdPattern(), from.getApiContextIdPattern())); + conf.setApiContextListing(or(conf.isApiContextListing(), from.isApiContextListing())); + conf.setApiVendorExtension(or(conf.isApiVendorExtension(), from.isApiVendorExtension())); + conf.setHostNameResolver(or(conf.getHostNameResolver(), from.getHostNameResolver(), + RestConfiguration.RestHostNameResolver.allLocalIp)); + conf.setBindingMode(or(conf.getBindingMode(), from.getBindingMode(), RestConfiguration.RestBindingMode.off)); + conf.setSkipBindingOnErrorCode(or(conf.isSkipBindingOnErrorCode(), from.isSkipBindingOnErrorCode())); + conf.setClientRequestValidation(or(conf.isClientRequestValidation(), from.isClientRequestValidation())); + conf.setEnableCORS(or(conf.isEnableCORS(), from.isEnableCORS())); + conf.setJsonDataFormat(or(conf.getJsonDataFormat(), from.getJsonDataFormat())); + conf.setXmlDataFormat(or(conf.getXmlDataFormat(), from.getXmlDataFormat())); + conf.setComponentProperties(mergeProperties(conf.getComponentProperties(), from.getComponentProperties())); + conf.setEndpointProperties(mergeProperties(conf.getEndpointProperties(), from.getEndpointProperties())); + conf.setConsumerProperties(mergeProperties(conf.getConsumerProperties(), from.getConsumerProperties())); + conf.setDataFormatProperties(mergeProperties(conf.getDataFormatProperties(), from.getDataFormatProperties())); + conf.setApiProperties(mergeProperties(conf.getApiProperties(), from.getApiProperties())); + conf.setCorsHeaders(mergeProperties(conf.getCorsHeaders(), from.getCorsHeaders())); + } + + return conf; + } + + private <T> T or(T t1, T t2) { + return t2 != null ? t2 : t1; + } + + private <T> T or(T t1, T t2, T def) { + return t2 != null && t2 != def ? t2 : t1; + } + + private <T> Map<String, T> mergeProperties(Map<String, T> base, Map<String, T> addons) { + if (base != null || addons != null) { + Map<String, T> result = new HashMap<>(); + if (base != null) { + result.putAll(base); + } + if (addons != null) { + result.putAll(addons); + } + return result; + } + return base; + } +} diff --git a/extensions/rest/runtime/src/main/java/org/apache/camel/quarkus/component/rest/QuarkusRestEndpoint.java b/extensions/rest/runtime/src/main/java/org/apache/camel/quarkus/component/rest/QuarkusRestEndpoint.java new file mode 100644 index 0000000..6c1281c --- /dev/null +++ b/extensions/rest/runtime/src/main/java/org/apache/camel/quarkus/component/rest/QuarkusRestEndpoint.java @@ -0,0 +1,204 @@ +/* + * 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.quarkus.component.rest; + +import java.util.Map; +import java.util.Set; + +import org.apache.camel.Component; +import org.apache.camel.ExtendedCamelContext; +import org.apache.camel.NoFactoryAvailableException; +import org.apache.camel.NoSuchBeanException; +import org.apache.camel.Producer; +import org.apache.camel.component.rest.RestComponent; +import org.apache.camel.component.rest.RestEndpoint; +import org.apache.camel.spi.FactoryFinder; +import org.apache.camel.spi.RestConfiguration; +import org.apache.camel.spi.RestProducerFactory; +import org.apache.camel.util.ObjectHelper; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import static org.apache.camel.support.RestProducerFactoryHelper.setupComponent; + +public class QuarkusRestEndpoint extends RestEndpoint { + private static final Logger LOG = LoggerFactory.getLogger(QuarkusRestEndpoint.class); + + public QuarkusRestEndpoint(String endpointUri, RestComponent component) { + super(endpointUri, component); + } + + @SuppressWarnings("unchecked") + @Override + public Producer createProducer() throws Exception { + if (ObjectHelper.isEmpty(getHost())) { + // hostname must be provided + throw new IllegalArgumentException("Hostname must be configured on either restConfiguration" + + " or in the rest endpoint uri as a query parameter with name host, eg rest:" + getMethod() + ":" + + getPath() + "?host=someserver"); + } + + RestProducerFactory apiDocFactory = null; + RestProducerFactory factory = null; + + if (getApiDoc() != null) { + LOG.debug("Discovering camel-openapi-java on classpath for using api-doc: {}", getApiDoc()); + // lookup on classpath using factory finder to automatic find it (just add camel-openapi-java to classpath etc) + FactoryFinder finder = null; + try { + finder = getCamelContext().adapt(ExtendedCamelContext.class).getFactoryFinder(RESOURCE_PATH); + apiDocFactory = finder.newInstance(DEFAULT_API_COMPONENT_NAME, RestProducerFactory.class).orElse(null); + if (apiDocFactory == null) { + throw new NoFactoryAvailableException("Cannot find camel-openapi-java on classpath"); + } + getParameters().put("apiDoc", getApiDoc()); + } catch (NoFactoryAvailableException e) { + try { + LOG.debug("Discovering camel-swagger-java on classpath as fallback for using api-doc: {}", getApiDoc()); + Object instance = finder.newInstance("swagger").get(); + if (instance instanceof RestProducerFactory) { + // this factory from camel-swagger-java will facade the http component in use + apiDocFactory = (RestProducerFactory) instance; + } + getParameters().put("apiDoc", getApiDoc()); + } catch (Exception ex) { + + throw new IllegalStateException( + "Cannot find camel-openapi-java neither camel-swagger-java on classpath to use with api-doc: " + + getApiDoc()); + } + + } + } + + String pname = getProducerComponentName(); + if (pname != null) { + Object comp = getCamelContext().getRegistry().lookupByName(pname); + if (comp instanceof RestProducerFactory) { + factory = (RestProducerFactory) comp; + } else { + comp = setupComponent(getProducerComponentName(), getCamelContext(), + (Map<String, Object>) getParameters().get("component")); + if (comp instanceof RestProducerFactory) { + factory = (RestProducerFactory) comp; + } + } + + if (factory == null) { + if (comp != null) { + throw new IllegalArgumentException("Component " + pname + " is not a RestProducerFactory"); + } else { + throw new NoSuchBeanException(getProducerComponentName(), RestProducerFactory.class.getName()); + } + } + } + + // try all components + if (factory == null) { + for (String name : getCamelContext().getComponentNames()) { + Component comp = setupComponent(name, getCamelContext(), + (Map<String, Object>) getParameters().get("component")); + if (comp instanceof RestProducerFactory) { + factory = (RestProducerFactory) comp; + pname = name; + break; + } + } + } + + // fallback to use consumer name as it may be producer capable too + if (pname == null && getConsumerComponentName() != null) { + String cname = getConsumerComponentName(); + Object comp = getCamelContext().getRegistry().lookupByName(cname); + if (comp instanceof RestProducerFactory) { + factory = (RestProducerFactory) comp; + pname = cname; + } else { + comp = setupComponent(cname, getCamelContext(), (Map<String, Object>) getParameters().get("component")); + if (comp instanceof RestProducerFactory) { + factory = (RestProducerFactory) comp; + pname = cname; + } + } + } + + getParameters().put("producerComponentName", pname); + + // lookup in registry + if (factory == null) { + Set<RestProducerFactory> factories = getCamelContext().getRegistry().findByType(RestProducerFactory.class); + if (factories != null && factories.size() == 1) { + factory = factories.iterator().next(); + } + } + + // no explicit factory found then try to see if we can find any of the default rest producer components + // and there must only be exactly one so we safely can pick this one + if (factory == null) { + RestProducerFactory found = null; + String foundName = null; + for (String name : DEFAULT_REST_PRODUCER_COMPONENTS) { + Object comp = setupComponent(name, getCamelContext(), (Map<String, Object>) getParameters().get("component")); + if (comp instanceof RestProducerFactory) { + if (found == null) { + found = (RestProducerFactory) comp; + foundName = name; + } else { + throw new IllegalArgumentException( + "Multiple RestProducerFactory found on classpath. Configure explicit which component to use"); + } + } + } + if (found != null) { + LOG.debug("Auto discovered {} as RestProducerFactory", foundName); + factory = found; + } + } + + if (factory != null) { + LOG.debug("Using RestProducerFactory: {}", factory); + + RestConfiguration config = getCamelContext().getRestConfiguration(pname, false); + if (config == null) { + config = getCamelContext().getRestConfiguration(); + } + if (config == null) { + config = getCamelContext().getRestConfiguration(pname, true); + } + + Producer producer; + if (apiDocFactory != null) { + // wrap the factory using the api doc factory which will use the factory + getParameters().put("restProducerFactory", factory); + producer = apiDocFactory.createProducer(getCamelContext(), getHost(), getMethod(), getPath(), getUriTemplate(), + getQueryParameters(), getConsumes(), getProduces(), config, getParameters()); + } else { + producer = factory.createProducer(getCamelContext(), getHost(), getMethod(), getPath(), getUriTemplate(), + getQueryParameters(), getConsumes(), getProduces(), config, getParameters()); + } + + QuarkusRestProducer answer = new QuarkusRestProducer(this, producer, config); + answer.setOutType(getOutType()); + answer.setType(getInType()); + answer.setBindingMode(getBindingMode()); + + return answer; + } else { + throw new IllegalStateException("Cannot find RestProducerFactory in Registry or as a Component to use"); + } + } +} diff --git a/extensions/rest/runtime/src/main/java/org/apache/camel/quarkus/component/rest/QuarkusRestProducer.java b/extensions/rest/runtime/src/main/java/org/apache/camel/quarkus/component/rest/QuarkusRestProducer.java new file mode 100644 index 0000000..b454cce --- /dev/null +++ b/extensions/rest/runtime/src/main/java/org/apache/camel/quarkus/component/rest/QuarkusRestProducer.java @@ -0,0 +1,215 @@ +/* + * 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.quarkus.component.rest; + +import java.util.HashMap; +import java.util.Map; + +import org.apache.camel.AsyncCallback; +import org.apache.camel.AsyncProcessor; +import org.apache.camel.CamelContext; +import org.apache.camel.Endpoint; +import org.apache.camel.Exchange; +import org.apache.camel.ExtendedCamelContext; +import org.apache.camel.Producer; +import org.apache.camel.component.rest.RestProducer; +import org.apache.camel.component.rest.RestProducerBindingProcessor; +import org.apache.camel.spi.BeanIntrospection; +import org.apache.camel.spi.DataFormat; +import org.apache.camel.spi.RestConfiguration; +import org.apache.camel.support.AsyncProcessorConverterHelper; +import org.apache.camel.support.PropertyBindingSupport; +import org.apache.camel.support.service.ServiceHelper; + +public class QuarkusRestProducer extends RestProducer { + private final RestConfiguration configuration; + private final CamelContext camelContext; + + // the producer of the Camel component that is used as the HTTP client to call the REST service + private AsyncProcessor producer; + // if binding is enabled then this processor should be used to wrap the call with binding before/after + private AsyncProcessor binding; + + public QuarkusRestProducer(Endpoint endpoint, Producer producer, RestConfiguration configuration) { + super(endpoint, producer, configuration); + + this.configuration = configuration; + this.camelContext = endpoint.getCamelContext(); + this.producer = AsyncProcessorConverterHelper.convert(producer); + } + + @Override + protected void doStart() throws Exception { + super.doStart(); + + // create binding processor (returns null if binding is not in use) + binding = createBindingProcessor(); + + ServiceHelper.startService(binding, producer); + } + + @Override + protected void doStop() throws Exception { + super.doStop(); + ServiceHelper.stopService(producer, binding); + } + + @Override + public boolean process(Exchange exchange, AsyncCallback callback) { + try { + prepareExchange(exchange); + if (binding != null) { + return binding.process(exchange, callback); + } else { + // no binding in use call the producer directly + return producer.process(exchange, callback); + } + } catch (Throwable e) { + exchange.setException(e); + callback.done(true); + return true; + } + } + + @Override + protected AsyncProcessor createBindingProcessor() throws Exception { + // these options can be overridden per endpoint + String mode = configuration.getBindingMode().name(); + if (getBindingMode() != null) { + mode = getBindingMode().name(); + } + boolean skip = configuration.isSkipBindingOnErrorCode(); + if (getSkipBindingOnErrorCode() != null) { + skip = getSkipBindingOnErrorCode(); + } + + if (mode == null || "off".equals(mode)) { + // binding mode is off + return null; + } + + // setup json data format + String name = configuration.getJsonDataFormat(); + if (name != null) { + // must only be a name, not refer to an existing instance + Object instance = camelContext.getRegistry().lookupByName(name); + if (instance != null) { + throw new IllegalArgumentException( + "JsonDataFormat name: " + name + " must not be an existing bean instance from the registry"); + } + } else { + name = "json-jackson"; + } + // this will create a new instance as the name was not already pre-created + DataFormat json = camelContext.resolveDataFormat(name); + DataFormat outJson = camelContext.resolveDataFormat(name); + + // is json binding required? + if (mode.contains("json") && json == null) { + throw new IllegalArgumentException("JSon DataFormat " + name + " not found."); + } + + BeanIntrospection beanIntrospection = camelContext.adapt(ExtendedCamelContext.class).getBeanIntrospection(); + if (json != null) { + Class<?> clazz = null; + if (getType() != null) { + String typeName = getType().endsWith("[]") ? getType().substring(0, getType().length() - 2) : getType(); + clazz = camelContext.getClassResolver().resolveMandatoryClass(typeName); + } + if (clazz != null) { + beanIntrospection.setProperty(camelContext, json, "unmarshalType", clazz); + beanIntrospection.setProperty(camelContext, json, "useList", getType().endsWith("[]")); + } + setAdditionalConfiguration(configuration, camelContext, json, "json.in."); + + Class<?> outClazz = null; + if (getOutType() != null) { + String typeName = getOutType().endsWith("[]") ? getOutType().substring(0, getOutType().length() - 2) + : getOutType(); + outClazz = camelContext.getClassResolver().resolveMandatoryClass(typeName); + } + if (outClazz != null) { + beanIntrospection.setProperty(camelContext, outJson, "unmarshalType", outClazz); + beanIntrospection.setProperty(camelContext, outJson, "useList", getOutType().endsWith("[]")); + } + setAdditionalConfiguration(configuration, camelContext, outJson, "json.out."); + } + + // setup xml data format + name = configuration.getXmlDataFormat(); + if (name != null) { + // must only be a name, not refer to an existing instance + Object instance = camelContext.getRegistry().lookupByName(name); + if (instance != null) { + throw new IllegalArgumentException( + "XmlDataFormat name: " + name + " must not be an existing bean instance from the registry"); + } + } else { + name = "jaxb"; + } + // this will create a new instance as the name was not already pre-created + DataFormat jaxb = camelContext.resolveDataFormat(name); + DataFormat outJaxb = camelContext.resolveDataFormat(name); + + // is xml binding required? + if (mode.contains("xml") && jaxb == null) { + throw new IllegalArgumentException("XML DataFormat " + name + " not found."); + } + + if (jaxb != null) { + throw new IllegalArgumentException( + "Unsupported XmlDataFormat name: " + name + ": Please add a dependency to camel-quarkus-xml-jaxb"); + } + + return new RestProducerBindingProcessor(producer, camelContext, json, jaxb, outJson, outJaxb, mode, skip, getOutType()); + } + + private void setAdditionalConfiguration(RestConfiguration config, CamelContext context, + DataFormat dataFormat, String prefix) throws Exception { + if (config.getDataFormatProperties() != null && !config.getDataFormatProperties().isEmpty()) { + // must use a copy as otherwise the options gets removed during introspection setProperties + Map<String, Object> copy = new HashMap<>(); + + // filter keys on prefix + // - either its a known prefix and must match the prefix parameter + // - or its a common configuration that we should always use + for (Map.Entry<String, Object> entry : config.getDataFormatProperties().entrySet()) { + String key = entry.getKey(); + String copyKey; + boolean known = isKeyKnownPrefix(key); + if (known) { + // remove the prefix from the key to use + copyKey = key.substring(prefix.length()); + } else { + // use the key as is + copyKey = key; + } + if (!known || key.startsWith(prefix)) { + copy.put(copyKey, entry.getValue()); + } + } + + // set reference properties first as they use # syntax that fools the regular properties setter + PropertyBindingSupport.bindProperties(context, dataFormat, copy); + } + } + + 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/extensions/rest/deployment/src/main/java/org/apache/camel/quarkus/component/rest/deployment/RestProcessor.java b/extensions/rest/runtime/src/main/java/org/apache/camel/quarkus/component/rest/RestRecorder.java similarity index 70% copy from extensions/rest/deployment/src/main/java/org/apache/camel/quarkus/component/rest/deployment/RestProcessor.java copy to extensions/rest/runtime/src/main/java/org/apache/camel/quarkus/component/rest/RestRecorder.java index daa2fdb..cef7d79 100644 --- a/extensions/rest/deployment/src/main/java/org/apache/camel/quarkus/component/rest/deployment/RestProcessor.java +++ b/extensions/rest/runtime/src/main/java/org/apache/camel/quarkus/component/rest/RestRecorder.java @@ -14,18 +14,14 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.apache.camel.quarkus.component.rest.deployment; +package org.apache.camel.quarkus.component.rest; -import io.quarkus.deployment.annotations.BuildStep; -import io.quarkus.deployment.builditem.FeatureBuildItem; +import io.quarkus.runtime.RuntimeValue; +import io.quarkus.runtime.annotations.Recorder; -class RestProcessor { - - private static final String FEATURE = "camel-rest"; - - @BuildStep - FeatureBuildItem feature() { - return new FeatureBuildItem(FEATURE); +@Recorder +public class RestRecorder { + public RuntimeValue<QuarkusRestComponent> createRestComponent() { + return new RuntimeValue<>(new QuarkusRestComponent()); } - } diff --git a/extensions/rest/deployment/src/main/java/org/apache/camel/quarkus/component/rest/deployment/RestProcessor.java b/extensions/rest/runtime/src/main/java/org/apache/camel/quarkus/component/rest/graal/NoJAXBContext.java similarity index 68% copy from extensions/rest/deployment/src/main/java/org/apache/camel/quarkus/component/rest/deployment/RestProcessor.java copy to extensions/rest/runtime/src/main/java/org/apache/camel/quarkus/component/rest/graal/NoJAXBContext.java index daa2fdb..6f67393 100644 --- a/extensions/rest/deployment/src/main/java/org/apache/camel/quarkus/component/rest/deployment/RestProcessor.java +++ b/extensions/rest/runtime/src/main/java/org/apache/camel/quarkus/component/rest/graal/NoJAXBContext.java @@ -14,18 +14,19 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.apache.camel.quarkus.component.rest.deployment; +package org.apache.camel.quarkus.component.rest.graal; -import io.quarkus.deployment.annotations.BuildStep; -import io.quarkus.deployment.builditem.FeatureBuildItem; +import java.util.function.BooleanSupplier; -class RestProcessor { +public final class NoJAXBContext implements BooleanSupplier { + @Override + public boolean getAsBoolean() { + try { + Class.forName("javax.xml.bind.JAXBContext"); + } catch (ClassNotFoundException e) { + return true; + } - private static final String FEATURE = "camel-rest"; - - @BuildStep - FeatureBuildItem feature() { - return new FeatureBuildItem(FEATURE); + return false; } - } diff --git a/extensions/rest/deployment/src/main/java/org/apache/camel/quarkus/component/rest/deployment/RestProcessor.java b/extensions/rest/runtime/src/main/java/org/apache/camel/quarkus/component/rest/graal/RestProducerSubstitution.java similarity index 59% copy from extensions/rest/deployment/src/main/java/org/apache/camel/quarkus/component/rest/deployment/RestProcessor.java copy to extensions/rest/runtime/src/main/java/org/apache/camel/quarkus/component/rest/graal/RestProducerSubstitution.java index daa2fdb..14511ed 100644 --- a/extensions/rest/deployment/src/main/java/org/apache/camel/quarkus/component/rest/deployment/RestProcessor.java +++ b/extensions/rest/runtime/src/main/java/org/apache/camel/quarkus/component/rest/graal/RestProducerSubstitution.java @@ -14,18 +14,17 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.apache.camel.quarkus.component.rest.deployment; +package org.apache.camel.quarkus.component.rest.graal; -import io.quarkus.deployment.annotations.BuildStep; -import io.quarkus.deployment.builditem.FeatureBuildItem; +import com.oracle.svm.core.annotate.Substitute; +import com.oracle.svm.core.annotate.TargetClass; +import org.apache.camel.AsyncProcessor; +import org.apache.camel.component.rest.RestProducer; -class RestProcessor { - - private static final String FEATURE = "camel-rest"; - - @BuildStep - FeatureBuildItem feature() { - return new FeatureBuildItem(FEATURE); +@TargetClass(value = RestProducer.class, onlyWith = NoJAXBContext.class) +final class RestProducerSubstitution { + @Substitute + protected AsyncProcessor createBindingProcessor() throws Exception { + throw new UnsupportedOperationException("Please add a dependency to camel-quarkus-xml-jaxb"); } - }
