This is an automated email from the ASF dual-hosted git repository. davsclaus pushed a commit to branch main in repository https://gitbox.apache.org/repos/asf/camel.git
commit edfd096b0dd96c889fe08f3c6d29a94d8b8c7a48 Author: Claus Ibsen <claus.ib...@gmail.com> AuthorDate: Thu Jan 20 20:19:02 2022 +0100 CAMEL-17520: camel-core - Endpoints which are lenient properties should be configured as optional. This fixes using square brackets in http endpoint query parameters. --- .../component/http/HttpSquareBracketTest.java | 72 ++++++++++++++++++++++ .../org/apache/camel/support/DefaultEndpoint.java | 5 +- .../camel/support/PropertyBindingSupport.java | 35 ++++++++--- 3 files changed, 102 insertions(+), 10 deletions(-) diff --git a/components/camel-http/src/test/java/org/apache/camel/component/http/HttpSquareBracketTest.java b/components/camel-http/src/test/java/org/apache/camel/component/http/HttpSquareBracketTest.java new file mode 100644 index 0000000..9571b73 --- /dev/null +++ b/components/camel-http/src/test/java/org/apache/camel/component/http/HttpSquareBracketTest.java @@ -0,0 +1,72 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.camel.component.http; + +import org.apache.camel.Exchange; +import org.apache.camel.component.http.handler.BasicValidationHandler; +import org.apache.http.impl.bootstrap.HttpServer; +import org.apache.http.impl.bootstrap.ServerBootstrap; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import static org.apache.camel.component.http.HttpMethods.GET; + +public class HttpSquareBracketTest extends BaseHttpTest { + + private HttpServer localServer; + + private String baseUrl; + + @BeforeEach + @Override + public void setUp() throws Exception { + super.setUp(); + + localServer = ServerBootstrap.bootstrap().setHttpProcessor(getBasicHttpProcessor()) + .setConnectionReuseStrategy(getConnectionReuseStrategy()).setResponseFactory(getHttpResponseFactory()) + .setExpectationVerifier(getHttpExpectationVerifier()).setSslContext(getSSLContext()) + .registerHandler("/", + new BasicValidationHandler( + GET.name(), "country=dk&filter[end-date]=2022-12-31&filter[start-date]=2022-01-01", null, + getExpectedContent())) + .create(); + localServer.start(); + + baseUrl = "http://" + localServer.getInetAddress().getHostName() + ":" + localServer.getLocalPort(); + } + + @AfterEach + @Override + public void tearDown() throws Exception { + super.tearDown(); + + if (localServer != null) { + localServer.stop(); + } + } + + @Test + public void httpSquare() throws Exception { + Exchange exchange = template.request(baseUrl + "/?country=dk&filter[start-date]=2022-01-01&filter[end-date]=2022-12-31", + exchange1 -> { + }); + + assertExchange(exchange); + } + +} diff --git a/core/camel-support/src/main/java/org/apache/camel/support/DefaultEndpoint.java b/core/camel-support/src/main/java/org/apache/camel/support/DefaultEndpoint.java index 0d40247..3d4f13f 100644 --- a/core/camel-support/src/main/java/org/apache/camel/support/DefaultEndpoint.java +++ b/core/camel-support/src/main/java/org/apache/camel/support/DefaultEndpoint.java @@ -410,7 +410,10 @@ public abstract class DefaultEndpoint extends ServiceSupport implements Endpoint configurer = ((PropertyConfigurerAware) bean).getPropertyConfigurer(bean); } // use configurer and ignore case as end users may type an option name with mixed case - PropertyBindingSupport.build().withConfigurer(configurer).withIgnoreCase(true).bind(camelContext, bean, parameters); + PropertyBindingSupport.build().withConfigurer(configurer).withIgnoreCase(true) + // if the endpoint is lenient then use optional + .withOptional(isLenientProperties()) + .bind(camelContext, bean, parameters); } /** diff --git a/core/camel-support/src/main/java/org/apache/camel/support/PropertyBindingSupport.java b/core/camel-support/src/main/java/org/apache/camel/support/PropertyBindingSupport.java index 789b13a..049ced6 100644 --- a/core/camel-support/src/main/java/org/apache/camel/support/PropertyBindingSupport.java +++ b/core/camel-support/src/main/java/org/apache/camel/support/PropertyBindingSupport.java @@ -163,6 +163,7 @@ public final class PropertyBindingSupport { * @param removeParameter whether to remove bound parameters * @param flattenProperties whether properties should be flattened (when properties is a map of maps) * @param mandatory whether all parameters must be bound + * @param optional whether parameters can be optional such as configuring endpoints that are lenient * @param nesting whether nesting is in use * @param deepNesting whether deep nesting is in use, where Camel will attempt to walk as deep as possible * by creating new objects in the OGNL graph if a property has a setter and the object @@ -178,7 +179,8 @@ public final class PropertyBindingSupport { */ private static boolean doBindProperties( CamelContext camelContext, Object target, Map<String, Object> properties, - String optionPrefix, boolean ignoreCase, boolean removeParameter, boolean flattenProperties, boolean mandatory, + String optionPrefix, boolean ignoreCase, boolean removeParameter, boolean flattenProperties, + boolean mandatory, boolean optional, boolean nesting, boolean deepNesting, boolean fluentBuilder, boolean allowPrivateSetter, boolean reference, boolean placeholder, boolean reflection, PropertyConfigurer configurer) { @@ -220,7 +222,7 @@ public final class PropertyBindingSupport { // attempt to bind the property boolean hit = doBuildPropertyOgnlPath(camelContext, target, key, value, deepNesting, fluentBuilder, - allowPrivateSetter, ignoreCase, reference, placeholder, mandatory, reflection, configurer); + allowPrivateSetter, ignoreCase, reference, placeholder, mandatory, optional, reflection, configurer); if (hit && removeParameter) { properties.remove(key); } @@ -233,12 +235,13 @@ public final class PropertyBindingSupport { private static boolean doBuildPropertyOgnlPath( final CamelContext camelContext, final Object originalTarget, String name, final Object value, boolean deepNesting, boolean fluentBuilder, boolean allowPrivateSetter, - boolean ignoreCase, boolean reference, boolean placeholder, boolean mandatory, + boolean ignoreCase, boolean reference, boolean placeholder, boolean mandatory, boolean optional, boolean reflection, PropertyConfigurer configurer) { - boolean optional = name.startsWith("?"); - if (optional) { + if (name.startsWith("?")) { + // the name marks the option as optional name = name.substring(1); + optional = true; } Object newTarget = originalTarget; @@ -467,7 +470,8 @@ public final class PropertyBindingSupport { } if (!bound && reflection) { // fallback to reflection based - bound = setPropertyCollectionViaReflection(camelContext, target, key, value, ignoreCase, reference); + bound = setPropertyCollectionViaReflection(camelContext, target, key, value, ignoreCase, reference, + optional); } } else { // regular key @@ -506,7 +510,7 @@ public final class PropertyBindingSupport { private static boolean setPropertyCollectionViaReflection( CamelContext context, Object target, String name, Object value, - boolean ignoreCase, boolean reference) + boolean ignoreCase, boolean reference, boolean optional) throws Exception { BeanIntrospection bi = context.adapt(ExtendedCamelContext.class).getBeanIntrospection(); @@ -537,6 +541,9 @@ public final class PropertyBindingSupport { } boolean hit = bi.setProperty(context, target, key, obj); if (!hit) { + if (optional) { + return false; + } throw new IllegalArgumentException( "Cannot set property: " + name + " as a Map because target bean has no setter method for the Map"); } @@ -1624,6 +1631,7 @@ public final class PropertyBindingSupport { private boolean removeParameters = true; private boolean flattenProperties; private boolean mandatory; + private boolean optional; private boolean nesting = true; private boolean deepNesting = true; private boolean reference = true; @@ -1702,6 +1710,14 @@ public final class PropertyBindingSupport { } /** + * Whether parameters can be optional such as configuring endpoints that are lenient + */ + public Builder withOptional(boolean optional) { + this.optional = optional; + return this; + } + + /** * Whether nesting is in use */ public Builder withNesting(boolean nesting) { @@ -1799,7 +1815,7 @@ public final class PropertyBindingSupport { } return doBindProperties(camelContext, target, removeParameters ? properties : new HashMap<>(properties), - optionPrefix, ignoreCase, removeParameters, flattenProperties, mandatory, + optionPrefix, ignoreCase, removeParameters, flattenProperties, mandatory, optional, nesting, deepNesting, fluentBuilder, allowPrivateSetter, reference, placeholder, reflection, configurer); } @@ -1817,7 +1833,7 @@ public final class PropertyBindingSupport { Map<String, Object> prop = properties != null ? properties : this.properties; return doBindProperties(context, obj, removeParameters ? prop : new HashMap<>(prop), - optionPrefix, ignoreCase, removeParameters, flattenProperties, mandatory, + optionPrefix, ignoreCase, removeParameters, flattenProperties, mandatory, optional, nesting, deepNesting, fluentBuilder, allowPrivateSetter, reference, placeholder, reflection, configurer); } @@ -1835,6 +1851,7 @@ public final class PropertyBindingSupport { properties.put(key, value); return doBindProperties(camelContext, target, properties, optionPrefix, ignoreCase, true, false, mandatory, + optional, nesting, deepNesting, fluentBuilder, allowPrivateSetter, reference, placeholder, reflection, configurer); }