camel-commits mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From lburgazz...@apache.org
Subject camel git commit: CAMEL-11118: PingCheck : validate rest component
Date Tue, 11 Apr 2017 12:10:25 GMT
Repository: camel
Updated Branches:
  refs/heads/master 95314057c -> 1c6cf329c


CAMEL-11118: PingCheck : validate rest component


Project: http://git-wip-us.apache.org/repos/asf/camel/repo
Commit: http://git-wip-us.apache.org/repos/asf/camel/commit/1c6cf329
Tree: http://git-wip-us.apache.org/repos/asf/camel/tree/1c6cf329
Diff: http://git-wip-us.apache.org/repos/asf/camel/diff/1c6cf329

Branch: refs/heads/master
Commit: 1c6cf329c3ea91a7ab4417bd0e9fa43d12f8b1d8
Parents: 9531405
Author: lburgazzoli <lburgazzoli@gmail.com>
Authored: Thu Apr 6 18:53:36 2017 +0200
Committer: lburgazzoli <lburgazzoli@gmail.com>
Committed: Tue Apr 11 14:09:33 2017 +0200

----------------------------------------------------------------------
 .../camel/component/rest/RestComponent.java     |  15 +-
 .../component/rest/RestComponentVerifier.java   | 152 ++++++++++++
 .../verifier/CatalogVerifierCustomizer.java     | 104 ++++++++
 .../impl/verifier/DefaultComponentVerifier.java |  60 +++--
 .../camel/impl/verifier/ResultBuilder.java      |   4 +
 .../camel/impl/verifier/ResultErrorBuilder.java |  16 ++
 .../apache/camel/util/function/Suppliers.java   |  14 ++
 .../util/function/ThrowingTriConsumer.java      |  22 ++
 .../apache/camel/util/function/TriConsumer.java |  22 ++
 .../rest/RestComponentVerifierTest.java         | 137 +++++++++++
 .../component/http4/HttpComponentVerifier.java  | 156 +++++++-----
 .../rest/RestCamelComponentVerifierTest.java    | 178 ++++++++++++++
 .../component/undertow/UndertowComponent.java   |  19 +-
 .../undertow/UndertowComponentVerifier.java     | 239 +++++++++++++++++++
 .../undertow/UndertowComponentVerifierTest.java | 103 ++++++++
 .../rest/RestUndertowComponentVerifierTest.java | 126 ++++++++++
 16 files changed, 1282 insertions(+), 85 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/camel/blob/1c6cf329/camel-core/src/main/java/org/apache/camel/component/rest/RestComponent.java
----------------------------------------------------------------------
diff --git a/camel-core/src/main/java/org/apache/camel/component/rest/RestComponent.java b/camel-core/src/main/java/org/apache/camel/component/rest/RestComponent.java
index 567d315..d1c1963 100644
--- a/camel-core/src/main/java/org/apache/camel/component/rest/RestComponent.java
+++ b/camel-core/src/main/java/org/apache/camel/component/rest/RestComponent.java
@@ -24,7 +24,9 @@ import java.util.function.Consumer;
 import java.util.function.Supplier;
 
 import org.apache.camel.CamelContext;
+import org.apache.camel.ComponentVerifier;
 import org.apache.camel.Endpoint;
+import org.apache.camel.VerifiableComponent;
 import org.apache.camel.impl.DefaultComponent;
 import org.apache.camel.model.rest.RestConstants;
 import org.apache.camel.spi.Metadata;
@@ -33,13 +35,13 @@ import org.apache.camel.util.CamelContextHelper;
 import org.apache.camel.util.FileUtil;
 import org.apache.camel.util.IntrospectionSupport;
 import org.apache.camel.util.ObjectHelper;
-import org.apache.camel.util.StringHelper;
 import org.apache.camel.util.URISupport;
 
 /**
  * Rest component.
  */
-public class RestComponent extends DefaultComponent {
+@Metadata(label = "verifiers", enums = "parameters,connectivity")
+public class RestComponent extends DefaultComponent implements VerifiableComponent {
 
     @Metadata(label = "common")
     private String componentName;
@@ -230,4 +232,13 @@ public class RestComponent extends DefaultComponent {
         }
     }
 
+    /**
+     * Get the {@link ComponentVerifier}
+     *
+     * @return the Component Verifier
+     */
+    @Override
+    public ComponentVerifier getVerifier() {
+        return new RestComponentVerifier(this);
+    }
 }

http://git-wip-us.apache.org/repos/asf/camel/blob/1c6cf329/camel-core/src/main/java/org/apache/camel/component/rest/RestComponentVerifier.java
----------------------------------------------------------------------
diff --git a/camel-core/src/main/java/org/apache/camel/component/rest/RestComponentVerifier.java b/camel-core/src/main/java/org/apache/camel/component/rest/RestComponentVerifier.java
new file mode 100644
index 0000000..5d19e0d
--- /dev/null
+++ b/camel-core/src/main/java/org/apache/camel/component/rest/RestComponentVerifier.java
@@ -0,0 +1,152 @@
+/**
+ * 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.rest;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import org.apache.camel.Component;
+import org.apache.camel.ComponentVerifier;
+import org.apache.camel.VerifiableComponent;
+import org.apache.camel.catalog.JSonSchemaHelper;
+import org.apache.camel.catalog.RuntimeCamelCatalog;
+import org.apache.camel.impl.verifier.CatalogVerifierCustomizer;
+import org.apache.camel.impl.verifier.DefaultComponentVerifier;
+import org.apache.camel.impl.verifier.ResultBuilder;
+import org.apache.camel.impl.verifier.ResultErrorBuilder;
+import org.apache.camel.spi.RestConsumerFactory;
+import org.apache.camel.spi.RestProducerFactory;
+import org.apache.camel.util.ObjectHelper;
+import org.apache.camel.util.function.Suppliers;
+
+public class RestComponentVerifier extends DefaultComponentVerifier {
+    private static final CatalogVerifierCustomizer CUSTOMIZER = new CatalogVerifierCustomizer().excludeUnknown();
+    private final RestComponent component;
+
+    RestComponentVerifier(RestComponent component) {
+        super("rest", component.getCamelContext());
+
+        this.component = component;
+    }
+
+    // *********************************
+    // Parameters validation
+    // *********************************
+
+    @Override
+    protected Result verifyParameters(Map<String, Object> parameters) {
+        ResultBuilder builder = ResultBuilder.withStatusAndScope(Result.Status.OK, Scope.PARAMETERS);
+
+        // Validate using the catalog but do not report unknown options as error
+        // as the may be used to customize the underlying component
+        super.verifyParametersAgainstCatalog(builder, parameters, CUSTOMIZER);
+
+        verifyUnderlyingComponent(Scope.PARAMETERS, builder, parameters);
+
+        return builder.build();
+    }
+
+    // *********************************
+    // Connectivity validation
+    // *********************************
+
+    @Override
+    protected Result verifyConnectivity(Map<String, Object> parameters) {
+        ResultBuilder builder = ResultBuilder.withStatusAndScope(Result.Status.OK, Scope.CONNECTIVITY);
+
+        verifyUnderlyingComponent(Scope.CONNECTIVITY, builder, parameters);
+
+        return builder.build();
+    }
+
+    // *********************************
+    // Helpers
+    // *********************************
+
+    protected void verifyUnderlyingComponent(Scope scope, ResultBuilder builder, Map<String, Object> parameters) {
+        // componentName is required for validation even at runtime camel might
+        // be able to find a suitable component at runtime.
+        String componentName = (String)parameters.get("componentName");
+        if (ObjectHelper.isNotEmpty(componentName)) {
+            try {
+                Component component = getTransportComponent(componentName);
+                if (component instanceof VerifiableComponent) {
+                    final RuntimeCamelCatalog catalog = getCamelContext().getRuntimeCamelCatalog();
+                    final String json = catalog.componentJSonSchema("rest");
+                    final Map<String, Object> restParameters = new HashMap<>(parameters);
+
+                    for (Map<String, String> m : JSonSchemaHelper.parseJsonSchema("componentProperties", json, true)) {
+                        String name = m.get("name");
+                        Object val = restParameters.remove(name);
+                        if (val != null) {
+                            // Add rest prefix to properties belonging to the rest
+                            // component so the underlying component know we want
+                            // to validate rest-related stuffs.
+                            restParameters.put("rest." + name, parameters.get(name));
+                        }
+                    }
+                    for (Map<String, String> m : JSonSchemaHelper.parseJsonSchema("properties", json, true)) {
+                        String name = m.get("name");
+                        Object val = restParameters.remove(name);
+                        if (val != null) {
+                            // Add rest prefix to properties belonging to the rest
+                            // component so the underlying component know we want
+                            // to validate rest-related stuffs.
+                            restParameters.put("rest." + name, parameters.get(name));
+                        }
+                    }
+
+                    ComponentVerifier verifier = ((VerifiableComponent)component).getVerifier();
+
+                    // restParameters now should contains rest-component related
+                    // properties with "rest." prefix and all the remaining can
+                    // be used to customize the underlying component (i.e. http
+                    // proxies, auth, etc)
+                    Result result = verifier.verify(scope, restParameters);
+
+                    // Combine errors and add an information about the component
+                    // they comes from
+                    for (VerificationError error : result.getErrors()) {
+                        builder.error(
+                            ResultErrorBuilder.fromError(error)
+                                .detail("component", componentName)
+                                .build()
+                        );
+                    }
+                } else {
+                    builder.error(
+                        ResultErrorBuilder.withUnsupportedComponent(componentName).build()
+                    );
+                }
+            } catch (Exception e) {
+                builder.error(
+                    ResultErrorBuilder.withException(e).build()
+                );
+            }
+        } else {
+            builder.error(ResultErrorBuilder.withMissingOption("componentName").build());
+        }
+    }
+
+    private Component getTransportComponent(String componentName) throws Exception {
+        return Suppliers.firstMatching(
+            comp -> comp != null && (comp instanceof RestConsumerFactory || comp instanceof RestProducerFactory),
+            () -> getCamelContext().getRegistry().lookupByNameAndType(componentName, Component.class),
+            () -> getCamelContext().getComponent(componentName, true, false)
+        ).orElse(null);
+    }
+}

http://git-wip-us.apache.org/repos/asf/camel/blob/1c6cf329/camel-core/src/main/java/org/apache/camel/impl/verifier/CatalogVerifierCustomizer.java
----------------------------------------------------------------------
diff --git a/camel-core/src/main/java/org/apache/camel/impl/verifier/CatalogVerifierCustomizer.java b/camel-core/src/main/java/org/apache/camel/impl/verifier/CatalogVerifierCustomizer.java
new file mode 100644
index 0000000..43d6993
--- /dev/null
+++ b/camel-core/src/main/java/org/apache/camel/impl/verifier/CatalogVerifierCustomizer.java
@@ -0,0 +1,104 @@
+/**
+ * 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.impl.verifier;
+
+public class CatalogVerifierCustomizer {
+    private boolean includeUnknown = true;
+    private boolean includeRequired = true;
+    private boolean includeInvalidBoolean = true;
+    private boolean includeInvalidInteger = true;
+    private boolean includeInvalidNumber = true;
+    private boolean includeInvalidEnum = true;
+
+    public boolean isIncludeUnknown() {
+        return includeUnknown;
+    }
+
+    public void setIncludeUnknown(boolean includeUnknown) {
+        this.includeUnknown = includeUnknown;
+    }
+
+    public boolean isIncludeRequired() {
+        return includeRequired;
+    }
+
+    public void setIncludeRequired(boolean includeRequired) {
+        this.includeRequired = includeRequired;
+    }
+
+    public boolean isIncludeInvalidBoolean() {
+        return includeInvalidBoolean;
+    }
+
+    public void setIncludeInvalidBoolean(boolean includeInvalidBoolean) {
+        this.includeInvalidBoolean = includeInvalidBoolean;
+    }
+
+    public boolean isIncludeInvalidInteger() {
+        return includeInvalidInteger;
+    }
+
+    public void setIncludeInvalidInteger(boolean includeInvalidInteger) {
+        this.includeInvalidInteger = includeInvalidInteger;
+    }
+
+    public boolean isIncludeInvalidNumber() {
+        return includeInvalidNumber;
+    }
+
+    public void setIncludeInvalidNumber(boolean includeInvalidNumber) {
+        this.includeInvalidNumber = includeInvalidNumber;
+    }
+
+    public boolean isIncludeInvalidEnum() {
+        return includeInvalidEnum;
+    }
+
+    public void setIncludeInvalidEnum(boolean includeInvalidEnum) {
+        this.includeInvalidEnum = includeInvalidEnum;
+    }
+
+    public CatalogVerifierCustomizer excludeUnknown() {
+        this.includeUnknown = false;
+        return this;
+    }
+
+    public CatalogVerifierCustomizer excludeRequired() {
+        this.includeRequired = false;
+        return this;
+    }
+
+    public CatalogVerifierCustomizer excludeInvalidBoolean() {
+        this.includeInvalidBoolean = false;
+        return this;
+    }
+
+    public CatalogVerifierCustomizer excludeInvalidInteger() {
+        this.includeInvalidInteger = false;
+        return this;
+    }
+
+    public CatalogVerifierCustomizer excludeInvalidNumber() {
+        this.includeInvalidNumber = false;
+        return this;
+    }
+
+    public CatalogVerifierCustomizer excludeInvalidEnum() {
+        this.includeInvalidEnum = false;
+        return this;
+    }
+}

http://git-wip-us.apache.org/repos/asf/camel/blob/1c6cf329/camel-core/src/main/java/org/apache/camel/impl/verifier/DefaultComponentVerifier.java
----------------------------------------------------------------------
diff --git a/camel-core/src/main/java/org/apache/camel/impl/verifier/DefaultComponentVerifier.java b/camel-core/src/main/java/org/apache/camel/impl/verifier/DefaultComponentVerifier.java
index 28908b0..b9d9998 100644
--- a/camel-core/src/main/java/org/apache/camel/impl/verifier/DefaultComponentVerifier.java
+++ b/camel-core/src/main/java/org/apache/camel/impl/verifier/DefaultComponentVerifier.java
@@ -62,7 +62,7 @@ public class DefaultComponentVerifier implements ComponentVerifier {
             return verifyConnectivity(parameters);
         }
 
-        throw new IllegalArgumentException("Unsupported Verifier scope: " + scope);
+        return ResultBuilder.unsupportedScope(scope).build();
     }
 
     protected Result verifyConnectivity(Map<String, Object> parameters) {
@@ -83,6 +83,10 @@ public class DefaultComponentVerifier implements ComponentVerifier {
     // *************************************
 
     protected void verifyParametersAgainstCatalog(ResultBuilder builder, Map<String, Object> parameters) {
+        verifyParametersAgainstCatalog(builder, parameters, new CatalogVerifierCustomizer());
+    }
+
+    protected void verifyParametersAgainstCatalog(ResultBuilder builder, Map<String, Object> parameters, CatalogVerifierCustomizer customizer) {
         String scheme = defaultScheme;
         if (parameters.containsKey("scheme")) {
             scheme = parameters.get("scheme").toString();
@@ -105,27 +109,39 @@ public class DefaultComponentVerifier implements ComponentVerifier {
         );
 
         if (!result.isSuccess()) {
-            stream(result.getUnknown())
-                .map(option -> ResultErrorBuilder.withUnknownOption(option).build())
-                .forEach(builder::error);
-            stream(result.getRequired())
-                .map(option -> ResultErrorBuilder.withMissingOption(option).build())
-                .forEach(builder::error);
-            stream(result.getInvalidBoolean())
-                .map(entry -> ResultErrorBuilder.withIllegalOption(entry.getKey(), entry.getValue()).build())
-                .forEach(builder::error);
-            stream(result.getInvalidInteger())
-                .map(entry -> ResultErrorBuilder.withIllegalOption(entry.getKey(), entry.getValue()).build())
-                .forEach(builder::error);
-            stream(result.getInvalidNumber())
-                .map(entry -> ResultErrorBuilder.withIllegalOption(entry.getKey(), entry.getValue()).build())
-                .forEach(builder::error);
-            stream(result.getInvalidEnum())
-                .map(entry ->
-                    ResultErrorBuilder.withIllegalOption(entry.getKey(), entry.getValue())
-                        .detail("enum.values", result.getEnumChoices(entry.getKey()))
-                        .build())
-                .forEach(builder::error);
+            if (customizer.isIncludeUnknown()) {
+                stream(result.getUnknown())
+                    .map(option -> ResultErrorBuilder.withUnknownOption(option).build())
+                    .forEach(builder::error);
+            }
+            if (customizer.isIncludeRequired()) {
+                stream(result.getRequired())
+                    .map(option -> ResultErrorBuilder.withMissingOption(option).build())
+                    .forEach(builder::error);
+            }
+            if (customizer.isIncludeInvalidBoolean()) {
+                stream(result.getInvalidBoolean())
+                    .map(entry -> ResultErrorBuilder.withIllegalOption(entry.getKey(), entry.getValue()).build())
+                    .forEach(builder::error);
+            }
+            if (customizer.isIncludeInvalidInteger()) {
+                stream(result.getInvalidInteger())
+                    .map(entry -> ResultErrorBuilder.withIllegalOption(entry.getKey(), entry.getValue()).build())
+                    .forEach(builder::error);
+            }
+            if (customizer.isIncludeInvalidNumber()) {
+                stream(result.getInvalidNumber())
+                    .map(entry -> ResultErrorBuilder.withIllegalOption(entry.getKey(), entry.getValue()).build())
+                    .forEach(builder::error);
+            }
+            if (customizer.isIncludeInvalidEnum()) {
+                stream(result.getInvalidEnum())
+                    .map(entry ->
+                        ResultErrorBuilder.withIllegalOption(entry.getKey(), entry.getValue())
+                            .detail("enum.values", result.getEnumChoices(entry.getKey()))
+                            .build())
+                    .forEach(builder::error);
+            }
         }
     }
 

http://git-wip-us.apache.org/repos/asf/camel/blob/1c6cf329/camel-core/src/main/java/org/apache/camel/impl/verifier/ResultBuilder.java
----------------------------------------------------------------------
diff --git a/camel-core/src/main/java/org/apache/camel/impl/verifier/ResultBuilder.java b/camel-core/src/main/java/org/apache/camel/impl/verifier/ResultBuilder.java
index ce049c0..5e8ddae 100644
--- a/camel-core/src/main/java/org/apache/camel/impl/verifier/ResultBuilder.java
+++ b/camel-core/src/main/java/org/apache/camel/impl/verifier/ResultBuilder.java
@@ -136,4 +136,8 @@ public final class ResultBuilder {
     public static ResultBuilder unsupported() {
         return withStatusAndScope(ComponentVerifier.Result.Status.UNSUPPORTED, ComponentVerifier.Scope.PARAMETERS);
     }
+
+    public static ResultBuilder unsupportedScope(ComponentVerifier.Scope scope) {
+        return withStatusAndScope(ComponentVerifier.Result.Status.UNSUPPORTED, scope);
+    }
 }

http://git-wip-us.apache.org/repos/asf/camel/blob/1c6cf329/camel-core/src/main/java/org/apache/camel/impl/verifier/ResultErrorBuilder.java
----------------------------------------------------------------------
diff --git a/camel-core/src/main/java/org/apache/camel/impl/verifier/ResultErrorBuilder.java b/camel-core/src/main/java/org/apache/camel/impl/verifier/ResultErrorBuilder.java
index dbef3e4..daadb48 100644
--- a/camel-core/src/main/java/org/apache/camel/impl/verifier/ResultErrorBuilder.java
+++ b/camel-core/src/main/java/org/apache/camel/impl/verifier/ResultErrorBuilder.java
@@ -101,6 +101,14 @@ public final class ResultErrorBuilder {
         return this;
     }
 
+    public ResultErrorBuilder details(Map<VerificationError.Attribute, Object> details) {
+        for (Map.Entry<VerificationError.Attribute, Object> entry : details.entrySet()) {
+            detail(entry.getKey(), entry.getValue());
+        }
+
+        return this;
+    }
+
     // **********************************
     // Build
     // **********************************
@@ -118,6 +126,14 @@ public final class ResultErrorBuilder {
     // Helpers
     // **********************************
 
+    public static ResultErrorBuilder fromError(VerificationError error) {
+        return new ResultErrorBuilder()
+            .code(error.getCode())
+            .description(error.getDescription())
+            .parameterKeys(error.getParameterKeys())
+            .details(error.getDetails());
+    }
+
     public static ResultErrorBuilder withCode(VerificationError.Code code) {
         return new ResultErrorBuilder().code(code);
     }

http://git-wip-us.apache.org/repos/asf/camel/blob/1c6cf329/camel-core/src/main/java/org/apache/camel/util/function/Suppliers.java
----------------------------------------------------------------------
diff --git a/camel-core/src/main/java/org/apache/camel/util/function/Suppliers.java b/camel-core/src/main/java/org/apache/camel/util/function/Suppliers.java
index b7d874c..84a43b2 100644
--- a/camel-core/src/main/java/org/apache/camel/util/function/Suppliers.java
+++ b/camel-core/src/main/java/org/apache/camel/util/function/Suppliers.java
@@ -20,6 +20,7 @@ import java.util.Objects;
 import java.util.Optional;
 import java.util.concurrent.atomic.AtomicReference;
 import java.util.function.Consumer;
+import java.util.function.Predicate;
 import java.util.function.Supplier;
 
 public final class Suppliers {
@@ -76,4 +77,17 @@ public final class Suppliers {
 
         return Optional.ofNullable(answer);
     }
+
+    public static <T> Optional<T> firstMatching(Predicate<T> predicate, ThrowingSupplier<T, Exception>... suppliers) throws Exception {
+        T answer = null;
+
+        for (int i = 0; i < suppliers.length; i++) {
+            answer = suppliers[i].get();
+            if (predicate.test(answer)) {
+                break;
+            }
+        }
+
+        return Optional.ofNullable(answer);
+    }
 }

http://git-wip-us.apache.org/repos/asf/camel/blob/1c6cf329/camel-core/src/main/java/org/apache/camel/util/function/ThrowingTriConsumer.java
----------------------------------------------------------------------
diff --git a/camel-core/src/main/java/org/apache/camel/util/function/ThrowingTriConsumer.java b/camel-core/src/main/java/org/apache/camel/util/function/ThrowingTriConsumer.java
new file mode 100644
index 0000000..ee3a626
--- /dev/null
+++ b/camel-core/src/main/java/org/apache/camel/util/function/ThrowingTriConsumer.java
@@ -0,0 +1,22 @@
+/**
+ * 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.util.function;
+
+@FunctionalInterface
+public interface ThrowingTriConsumer<I1, I2, I3, T extends Throwable> {
+    void accept(I1 i1, I2 i2, I3 i3) throws T;
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/camel/blob/1c6cf329/camel-core/src/main/java/org/apache/camel/util/function/TriConsumer.java
----------------------------------------------------------------------
diff --git a/camel-core/src/main/java/org/apache/camel/util/function/TriConsumer.java b/camel-core/src/main/java/org/apache/camel/util/function/TriConsumer.java
new file mode 100644
index 0000000..f119d92
--- /dev/null
+++ b/camel-core/src/main/java/org/apache/camel/util/function/TriConsumer.java
@@ -0,0 +1,22 @@
+/**
+ * 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.util.function;
+
+@FunctionalInterface
+public interface TriConsumer<I1, I2, I3> {
+    void accept(I1 i1, I2 i2, I3 i3);
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/camel/blob/1c6cf329/camel-core/src/test/java/org/apache/camel/component/rest/RestComponentVerifierTest.java
----------------------------------------------------------------------
diff --git a/camel-core/src/test/java/org/apache/camel/component/rest/RestComponentVerifierTest.java b/camel-core/src/test/java/org/apache/camel/component/rest/RestComponentVerifierTest.java
new file mode 100644
index 0000000..73fb130
--- /dev/null
+++ b/camel-core/src/test/java/org/apache/camel/component/rest/RestComponentVerifierTest.java
@@ -0,0 +1,137 @@
+/**
+ * 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.rest;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import org.apache.camel.CamelContext;
+import org.apache.camel.ComponentVerifier;
+import org.apache.camel.Consumer;
+import org.apache.camel.ContextTestSupport;
+import org.apache.camel.Endpoint;
+import org.apache.camel.Processor;
+import org.apache.camel.Producer;
+import org.apache.camel.VerifiableComponent;
+import org.apache.camel.impl.DefaultComponent;
+import org.apache.camel.impl.JndiRegistry;
+import org.apache.camel.impl.verifier.ResultBuilder;
+import org.apache.camel.impl.verifier.ResultErrorHelper;
+import org.apache.camel.spi.RestConfiguration;
+import org.apache.camel.spi.RestConsumerFactory;
+import org.apache.camel.spi.RestProducerFactory;
+import org.junit.Assert;
+
+public class RestComponentVerifierTest extends ContextTestSupport {
+    @Override
+    protected JndiRegistry createRegistry() throws Exception {
+        JndiRegistry registry = super.createRegistry();
+        registry.bind("rest", new RestComponent());
+        registry.bind("rest-component", new MyComponent());
+        return registry;
+    }
+
+    public void testParameters() throws Exception {
+        RestComponent component = context.getComponent("rest", RestComponent.class);
+        RestComponentVerifier verifier = (RestComponentVerifier)component.getVerifier();
+
+        Map<String, Object> parameters = new HashMap<>();
+        parameters.put("componentName", "rest-component");
+        parameters.put("host", "http://localhost:1234");
+        parameters.put("path", "verify");
+        parameters.put("method", "get");
+
+        // This parameter does not belong to the rest component and validation
+        // is delegated to the underlying component
+        parameters.put("authProxy", "http://localhost:8080");
+
+        ComponentVerifier.Result result = verifier.verify(ComponentVerifier.Scope.PARAMETERS, parameters);
+
+        Assert.assertEquals(ComponentVerifier.Result.Status.OK, result.getStatus());
+    }
+
+    public void testMissingParameters() throws Exception {
+        RestComponent component = context.getComponent("rest", RestComponent.class);
+        RestComponentVerifier verifier = (RestComponentVerifier)component.getVerifier();
+
+        Map<String, Object> parameters = new HashMap<>();
+        parameters.put("componentName", "rest-component");
+        parameters.put("host", "http://localhost:" + 1234);
+        parameters.put("path", "verify");
+
+        // This parameter does not belong to the rest component and validation
+        // is delegated to the underlying component
+        parameters.put("authProxy", "http://localhost:8080");
+
+        ComponentVerifier.Result result = verifier.verify(ComponentVerifier.Scope.PARAMETERS, parameters);
+
+        Assert.assertEquals(ComponentVerifier.Result.Status.ERROR, result.getStatus());
+        Assert.assertEquals(1, result.getErrors().size());
+        Assert.assertEquals(ComponentVerifier.VerificationError.StandardCode.MISSING_PARAMETER, result.getErrors().get(0).getCode());
+        Assert.assertEquals(1, result.getErrors().get(0).getParameterKeys().size());
+        Assert.assertTrue(result.getErrors().get(0).getParameterKeys().contains("method"));
+    }
+
+    // ***************************************************
+    //
+    // ***************************************************
+
+    private final class MyComponent extends DefaultComponent implements RestProducerFactory, RestConsumerFactory, VerifiableComponent {
+        @Override
+        protected Endpoint createEndpoint(String uri, String remaining, Map<String, Object> parameters) throws Exception {
+            throw new UnsupportedOperationException();
+        }
+
+        @Override
+        public Producer createProducer(
+                CamelContext camelContext,
+                String host,
+                String verb,
+                String basePath,
+                String uriTemplate,
+                String queryParameters,
+                String consumes,
+                String produces,
+                Map<String, Object> parameters)
+                    throws Exception {
+            throw new UnsupportedOperationException();
+        }
+
+        @Override
+        public Consumer createConsumer(
+                CamelContext camelContext,
+                Processor processor,
+                String verb,
+                String basePath,
+                String uriTemplate,
+                String consumes,
+                String produces,
+                RestConfiguration configuration,
+                Map<String, Object> parameters)
+                    throws Exception {
+            throw new UnsupportedOperationException();
+        }
+
+        @Override
+        public ComponentVerifier getVerifier() {
+            return (scope, parameters) ->
+                ResultBuilder.withStatusAndScope(ComponentVerifier.Result.Status.OK, scope)
+                    .error(ResultErrorHelper.requiresOption("authProxy", parameters))
+                    .build();
+        }
+    }
+}

http://git-wip-us.apache.org/repos/asf/camel/blob/1c6cf329/components/camel-http4/src/main/java/org/apache/camel/component/http4/HttpComponentVerifier.java
----------------------------------------------------------------------
diff --git a/components/camel-http4/src/main/java/org/apache/camel/component/http4/HttpComponentVerifier.java b/components/camel-http4/src/main/java/org/apache/camel/component/http4/HttpComponentVerifier.java
index ed55b12..46e3bc4 100644
--- a/components/camel-http4/src/main/java/org/apache/camel/component/http4/HttpComponentVerifier.java
+++ b/components/camel-http4/src/main/java/org/apache/camel/component/http4/HttpComponentVerifier.java
@@ -17,15 +17,16 @@
 package org.apache.camel.component.http4;
 
 import java.net.UnknownHostException;
+import java.util.HashMap;
 import java.util.Map;
 import java.util.Optional;
 
-import org.apache.camel.ComponentVerifier;
 import org.apache.camel.http.common.HttpHelper;
 import org.apache.camel.impl.verifier.DefaultComponentVerifier;
 import org.apache.camel.impl.verifier.ResultBuilder;
 import org.apache.camel.impl.verifier.ResultErrorBuilder;
-import org.apache.camel.impl.verifier.ResultErrorHelper;
+import org.apache.camel.util.FileUtil;
+import org.apache.camel.util.ObjectHelper;
 import org.apache.http.client.config.RequestConfig;
 import org.apache.http.client.methods.CloseableHttpResponse;
 import org.apache.http.client.methods.HttpGet;
@@ -48,11 +49,23 @@ final class HttpComponentVerifier extends DefaultComponentVerifier {
 
     @Override
     protected Result verifyParameters(Map<String, Object> parameters) {
-        // The default is success
-        ResultBuilder builder = ResultBuilder.withStatusAndScope(Result.Status.OK, Scope.PARAMETERS);
+        // Default is success
+        final ResultBuilder builder = ResultBuilder.withStatusAndScope(Result.Status.OK, Scope.PARAMETERS);
+        // Make a copy to avoid clashing with parent validation
+        final HashMap<String, Object> verifyParams = new HashMap<>(parameters);
+        // Check if validation is rest-related
+        final boolean isRest = verifyParams.entrySet().stream().anyMatch(e -> e.getKey().startsWith("rest."));
+
+        if (isRest) {
+            // Build the httpUri from rest configuration
+            verifyParams.put("httpUri", buildHttpUriFromRestParameters(parameters));
+
+            // Cleanup parameters map from rest related stuffs
+            verifyParams.entrySet().removeIf(e -> e.getKey().startsWith("rest."));
+        }
 
         // Validate using the catalog
-        super.verifyParametersAgainstCatalog(builder, parameters);
+        super.verifyParametersAgainstCatalog(builder, verifyParams);
 
         return builder.build();
     }
@@ -64,71 +77,98 @@ final class HttpComponentVerifier extends DefaultComponentVerifier {
     @Override
     protected Result verifyConnectivity(Map<String, Object> parameters) {
         // Default is success
-        ResultBuilder builder = ResultBuilder.withStatusAndScope(Result.Status.OK, Scope.CONNECTIVITY);
-
-        Optional<String> uri = getOption(parameters, "httpUri", String.class);
-        if (!uri.isPresent()) {
-            // lack of httpUri is a blocking issue
-            builder.error(ResultErrorHelper.requiresOption("httpUri", parameters));
-        } else {
-            builder.error(parameters, this::verifyHttpConnectivity);
+        final ResultBuilder builder = ResultBuilder.withStatusAndScope(Result.Status.OK, Scope.CONNECTIVITY);
+        // Make a copy to avoid clashing with parent validation
+        final HashMap<String, Object> verifyParams = new HashMap<>(parameters);
+        // Check if validation is rest-related
+        final boolean isRest = verifyParams.entrySet().stream().anyMatch(e -> e.getKey().startsWith("rest."));
+
+        if (isRest) {
+            // Build the httpUri from rest configuration
+            verifyParams.put("httpUri", buildHttpUriFromRestParameters(parameters));
+
+            // Cleanup parameters from rest related stuffs
+            verifyParams.entrySet().removeIf(e -> e.getKey().startsWith("rest."));
         }
 
-        return builder.build();
-    }
-
-    private void verifyHttpConnectivity(ResultBuilder builder, Map<String, Object> parameters) throws Exception {
-        Optional<String> uri = getOption(parameters, "httpUri", String.class);
-
-        CloseableHttpClient httpclient = createHttpClient(parameters);
-        HttpUriRequest request = new HttpGet(uri.get());
-
-        try (CloseableHttpResponse response = httpclient.execute(request)) {
-            int code = response.getStatusLine().getStatusCode();
-            String okCodes = getOption(parameters, "okStatusCodeRange", String.class).orElse("200-299");
-
-            if (!HttpHelper.isStatusCodeOk(code, okCodes)) {
-                if (code == 401) {
-                    // Unauthorized, add authUsername and authPassword to the list
-                    // of parameters in error
-                    builder.error(
-                        ResultErrorBuilder.withHttpCode(code)
-                            .description(response.getStatusLine().getReasonPhrase())
-                            .parameterKey("authUsername")
-                            .parameterKey("authPassword")
-                            .build()
-                    );
-                } else if (code >= 300 && code < 400) {
-                    // redirect
-                    builder.error(
-                        ResultErrorBuilder.withHttpCode(code)
-                            .description(response.getStatusLine().getReasonPhrase())
-                            .parameterKey("httpUri")
-                            .detail(VerificationError.HttpAttribute.HTTP_REDIRECT, () -> HttpUtil.responseHeaderValue(response, "location"))
-                            .build()
-                    );
-                } else if (code >= 400) {
-                    // generic http error
-                    builder.error(
-                        ResultErrorBuilder.withHttpCode(code)
-                            .description(response.getStatusLine().getReasonPhrase())
-                            .build()
-                    );
-                }
-            }
-        } catch (UnknownHostException e) {
+        String httpUri = getOption(verifyParams, "httpUri", String.class).orElse(null);
+        if (ObjectHelper.isEmpty(httpUri)) {
             builder.error(
-                ResultErrorBuilder.withException(e)
-                    .parameterKey("httpUri")
+                ResultErrorBuilder.withMissingOption("httpUri")
+                    .detail("rest", isRest)
                     .build()
             );
         }
+
+        try {
+            CloseableHttpClient httpclient = createHttpClient(verifyParams);
+            HttpUriRequest request = new HttpGet(httpUri);
+
+            try (CloseableHttpResponse response = httpclient.execute(request)) {
+                int code = response.getStatusLine().getStatusCode();
+                String okCodes = getOption(verifyParams, "okStatusCodeRange", String.class).orElse("200-299");
+
+                if (!HttpHelper.isStatusCodeOk(code, okCodes)) {
+                    if (code == 401) {
+                        // Unauthorized, add authUsername and authPassword to the list
+                        // of parameters in error
+                        builder.error(
+                            ResultErrorBuilder.withHttpCode(code)
+                                .description(response.getStatusLine().getReasonPhrase())
+                                .parameterKey("authUsername")
+                                .parameterKey("authPassword")
+                                .build()
+                        );
+                    } else if (code >= 300 && code < 400) {
+                        // redirect
+                        builder.error(
+                            ResultErrorBuilder.withHttpCode(code)
+                                .description(response.getStatusLine().getReasonPhrase())
+                                .parameterKey("httpUri")
+                                .detail(VerificationError.HttpAttribute.HTTP_REDIRECT, () -> HttpUtil.responseHeaderValue(response, "location"))
+                                .build()
+                        );
+                    } else if (code >= 400) {
+                        // generic http error
+                        builder.error(
+                            ResultErrorBuilder.withHttpCode(code)
+                                .description(response.getStatusLine().getReasonPhrase())
+                                .build()
+                        );
+                    }
+                }
+            } catch (UnknownHostException e) {
+                builder.error(
+                    ResultErrorBuilder.withException(e)
+                        .parameterKey("httpUri")
+                        .build()
+                );
+            }
+        } catch (Exception e) {
+            builder.error(ResultErrorBuilder.withException(e).build());
+        }
+
+        return builder.build();
     }
 
     // *********************************
     // Helpers
     // *********************************
 
+    private String buildHttpUriFromRestParameters(Map<String, Object> parameters) {
+        // We are doing rest endpoint validation but as today the endpoint
+        // can't do any param substitution so the validation is performed
+        // against the http uri
+        String httpUri = getOption(parameters, "rest.host", String.class).orElse(null);
+        String path = getOption(parameters, "rest.path", String.class).map(FileUtil::stripLeadingSeparator).orElse(null);
+
+        if (ObjectHelper.isNotEmpty(httpUri) && ObjectHelper.isNotEmpty(path)) {
+            httpUri = httpUri + "/" + path;
+        }
+
+        return httpUri;
+    }
+
     private Optional<HttpClientConfigurer> configureAuthentication(Map<String, Object> parameters) {
         Optional<String> authUsername = getOption(parameters, "authUsername", String.class);
         Optional<String> authPassword = getOption(parameters, "authPassword", String.class);

http://git-wip-us.apache.org/repos/asf/camel/blob/1c6cf329/components/camel-http4/src/test/java/org/apache/camel/component/http4/rest/RestCamelComponentVerifierTest.java
----------------------------------------------------------------------
diff --git a/components/camel-http4/src/test/java/org/apache/camel/component/http4/rest/RestCamelComponentVerifierTest.java b/components/camel-http4/src/test/java/org/apache/camel/component/http4/rest/RestCamelComponentVerifierTest.java
new file mode 100644
index 0000000..7fb540f
--- /dev/null
+++ b/components/camel-http4/src/test/java/org/apache/camel/component/http4/rest/RestCamelComponentVerifierTest.java
@@ -0,0 +1,178 @@
+/**
+ * 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.http4.rest;
+
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.Map;
+
+import org.apache.camel.ComponentVerifier;
+import org.apache.camel.component.http4.BaseHttpTest;
+import org.apache.camel.component.http4.handler.BasicValidationHandler;
+import org.apache.camel.component.rest.RestComponent;
+import org.apache.http.impl.bootstrap.HttpServer;
+import org.apache.http.impl.bootstrap.ServerBootstrap;
+import org.apache.http.localserver.RequestBasicAuth;
+import org.apache.http.localserver.ResponseBasicUnauthorized;
+import org.apache.http.protocol.HttpProcessor;
+import org.apache.http.protocol.ImmutableHttpProcessor;
+import org.apache.http.protocol.ResponseContent;
+import org.junit.After;
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Test;
+
+public class RestCamelComponentVerifierTest extends BaseHttpTest {
+    private HttpServer localServer;
+
+    @Before
+    @Override
+    public void setUp() throws Exception {
+        localServer = ServerBootstrap.bootstrap()
+            .setHttpProcessor(getHttpProcessor())
+            .registerHandler("/verify", new BasicValidationHandler("GET", null, null, getExpectedContent()))
+            .create();
+
+        localServer.start();
+
+        super.setUp();
+    }
+
+    @After
+    @Override
+    public void tearDown() throws Exception {
+        super.tearDown();
+
+        if (localServer != null) {
+            localServer.stop();
+        }
+    }
+
+    @Override
+    public boolean isUseRouteBuilder() {
+        return false;
+    }
+
+    private HttpProcessor getHttpProcessor() {
+        return new ImmutableHttpProcessor(
+            Arrays.asList(
+                new RequestBasicAuth()
+            ),
+            Arrays.asList(
+                new ResponseContent(),
+                new ResponseBasicUnauthorized())
+        );
+    }
+
+    // *************************************************
+    // Helpers
+    // *************************************************
+
+    protected String getLocalServerUri(String contextPath) {
+        return new StringBuilder()
+            .append("http://")
+            .append(localServer.getInetAddress().getHostName())
+            .append(":")
+            .append(localServer.getLocalPort())
+            .append(contextPath != null
+                ? contextPath.startsWith("/") ? contextPath : "/" + contextPath
+                : "")
+            .toString();
+    }
+
+    // *************************************************
+    // Tests
+    // *************************************************
+    @Test
+    public void testParameters() throws Exception {
+        RestComponent component = context().getComponent("rest", RestComponent.class);
+        ComponentVerifier verifier = component.getVerifier();
+
+        Map<String, Object> parameters = new HashMap<>();
+        parameters.put("componentName", "http4");
+        parameters.put("host", "http://localhost:" + localServer.getLocalPort());
+        parameters.put("path", "verify");
+        parameters.put("method", "get");
+
+        ComponentVerifier.Result result = verifier.verify(ComponentVerifier.Scope.PARAMETERS, parameters);
+
+        Assert.assertEquals(ComponentVerifier.Result.Status.OK, result.getStatus());
+    }
+
+    @Test
+    public void testMissingRestParameters() throws Exception {
+        RestComponent component = context.getComponent("rest", RestComponent.class);
+        ComponentVerifier verifier = component.getVerifier();
+
+        Map<String, Object> parameters = new HashMap<>();
+        parameters.put("componentName", "http4");
+        parameters.put("host", "http://localhost:" + localServer.getLocalPort());
+        parameters.put("path", "verify");
+
+        // This parameter does not belong to the rest component and validation
+        // is delegated to the transport component
+        parameters.put("copyHeaders", false);
+
+        ComponentVerifier.Result result = verifier.verify(ComponentVerifier.Scope.PARAMETERS, parameters);
+
+        Assert.assertEquals(ComponentVerifier.Result.Status.ERROR, result.getStatus());
+        Assert.assertEquals(1, result.getErrors().size());
+        Assert.assertEquals(ComponentVerifier.VerificationError.StandardCode.MISSING_PARAMETER, result.getErrors().get(0).getCode());
+        Assert.assertEquals(1, result.getErrors().get(0).getParameterKeys().size());
+        Assert.assertTrue(result.getErrors().get(0).getParameterKeys().contains("method"));
+    }
+
+    @Test
+    public void testWrongComponentParameters() throws Exception {
+        RestComponent component = context.getComponent("rest", RestComponent.class);
+        ComponentVerifier verifier = component.getVerifier();
+
+        Map<String, Object> parameters = new HashMap<>();
+        parameters.put("componentName", "http4");
+        parameters.put("host", "http://localhost:" + localServer.getLocalPort());
+        parameters.put("path", "verify");
+        parameters.put("method", "get");
+
+        // This parameter does not belong to the rest component and validation
+        // is delegated to the transport component
+        parameters.put("nonExistingOption", true);
+
+        ComponentVerifier.Result result = verifier.verify(ComponentVerifier.Scope.PARAMETERS, parameters);
+
+        Assert.assertEquals(ComponentVerifier.Result.Status.ERROR, result.getStatus());
+        Assert.assertEquals(1, result.getErrors().size());
+        Assert.assertEquals(ComponentVerifier.VerificationError.StandardCode.UNKNOWN_PARAMETER, result.getErrors().get(0).getCode());
+        Assert.assertEquals(1, result.getErrors().get(0).getParameterKeys().size());
+        Assert.assertTrue(result.getErrors().get(0).getParameterKeys().contains("nonExistingOption"));
+    }
+
+    @Test
+    public void testConnectivity() throws Exception {
+        RestComponent component = context().getComponent("rest", RestComponent.class);
+        ComponentVerifier verifier = component.getVerifier();
+
+        Map<String, Object> parameters = new HashMap<>();
+        parameters.put("componentName", "http4");
+        parameters.put("host", "http://localhost:" + localServer.getLocalPort());
+        parameters.put("path", "verify");
+        parameters.put("method", "get");
+
+        ComponentVerifier.Result result = verifier.verify(ComponentVerifier.Scope.CONNECTIVITY, parameters);
+
+        Assert.assertEquals(ComponentVerifier.Result.Status.OK, result.getStatus());
+    }
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/camel/blob/1c6cf329/components/camel-undertow/src/main/java/org/apache/camel/component/undertow/UndertowComponent.java
----------------------------------------------------------------------
diff --git a/components/camel-undertow/src/main/java/org/apache/camel/component/undertow/UndertowComponent.java b/components/camel-undertow/src/main/java/org/apache/camel/component/undertow/UndertowComponent.java
index 09d2ee0..86760ab 100644
--- a/components/camel-undertow/src/main/java/org/apache/camel/component/undertow/UndertowComponent.java
+++ b/components/camel-undertow/src/main/java/org/apache/camel/component/undertow/UndertowComponent.java
@@ -24,11 +24,13 @@ import java.util.Map;
 import java.util.concurrent.ConcurrentHashMap;
 
 import org.apache.camel.CamelContext;
+import org.apache.camel.ComponentVerifier;
 import org.apache.camel.Consumer;
 import org.apache.camel.Endpoint;
 import org.apache.camel.Processor;
 import org.apache.camel.Producer;
-import org.apache.camel.impl.UriEndpointComponent;
+import org.apache.camel.VerifiableComponent;
+import org.apache.camel.impl.DefaultComponent;
 import org.apache.camel.spi.Metadata;
 import org.apache.camel.spi.RestApiConsumerFactory;
 import org.apache.camel.spi.RestConfiguration;
@@ -48,7 +50,8 @@ import org.slf4j.LoggerFactory;
 /**
  * Represents the component that manages {@link UndertowEndpoint}.
  */
-public class UndertowComponent extends UriEndpointComponent implements RestConsumerFactory, RestApiConsumerFactory, RestProducerFactory {
+@Metadata(label = "verifiers", enums = "parameters,connectivity")
+public class UndertowComponent extends DefaultComponent implements RestConsumerFactory, RestApiConsumerFactory, RestProducerFactory, VerifiableComponent {
     private static final Logger LOG = LoggerFactory.getLogger(UndertowEndpoint.class);
 
     private Map<UndertowHostKey, UndertowHost> undertowRegistry = new ConcurrentHashMap<UndertowHostKey, UndertowHost>();
@@ -61,7 +64,10 @@ public class UndertowComponent extends UriEndpointComponent implements RestConsu
     private UndertowHostOptions hostOptions;
 
     public UndertowComponent() {
-        super(UndertowEndpoint.class);
+    }
+
+    public UndertowComponent(CamelContext context) {
+        super(context);
     }
 
     @Override
@@ -326,4 +332,11 @@ public class UndertowComponent extends UriEndpointComponent implements RestConsu
         this.hostOptions = hostOptions;
     }
 
+    /**
+     *
+     */
+    public ComponentVerifier getVerifier() {
+        return new UndertowComponentVerifier(this);
+    }
+
 }

http://git-wip-us.apache.org/repos/asf/camel/blob/1c6cf329/components/camel-undertow/src/main/java/org/apache/camel/component/undertow/UndertowComponentVerifier.java
----------------------------------------------------------------------
diff --git a/components/camel-undertow/src/main/java/org/apache/camel/component/undertow/UndertowComponentVerifier.java b/components/camel-undertow/src/main/java/org/apache/camel/component/undertow/UndertowComponentVerifier.java
new file mode 100644
index 0000000..637820d
--- /dev/null
+++ b/components/camel-undertow/src/main/java/org/apache/camel/component/undertow/UndertowComponentVerifier.java
@@ -0,0 +1,239 @@
+/**
+ * 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.undertow;
+
+import java.io.IOException;
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Optional;
+
+import io.undertow.client.ClientCallback;
+import io.undertow.client.ClientConnection;
+import io.undertow.client.ClientExchange;
+import io.undertow.client.ClientRequest;
+import io.undertow.client.ClientResponse;
+import io.undertow.client.UndertowClient;
+import io.undertow.connector.ByteBufferPool;
+import io.undertow.server.DefaultByteBufferPool;
+import io.undertow.util.Headers;
+import io.undertow.util.HttpString;
+import io.undertow.util.Methods;
+import org.apache.camel.impl.verifier.DefaultComponentVerifier;
+import org.apache.camel.impl.verifier.ResultBuilder;
+import org.apache.camel.impl.verifier.ResultErrorBuilder;
+import org.apache.camel.util.FileUtil;
+import org.apache.camel.util.ObjectHelper;
+import org.apache.camel.util.URISupport;
+import org.apache.camel.util.UnsafeUriCharactersEncoder;
+import org.xnio.AbstractIoFuture;
+import org.xnio.IoFuture;
+import org.xnio.OptionMap;
+import org.xnio.Xnio;
+import org.xnio.XnioWorker;
+
+public final class UndertowComponentVerifier extends DefaultComponentVerifier {
+    private final UndertowComponent component;
+
+    public UndertowComponentVerifier(UndertowComponent component) {
+        super("undertow", component.getCamelContext());
+
+        this.component = component;
+    }
+
+    // *********************************
+    // Parameters validation
+    // *********************************
+
+    @Override
+    protected Result verifyParameters(Map<String, Object> parameters) {
+        // Default is success
+        final ResultBuilder builder = ResultBuilder.withStatusAndScope(Result.Status.OK, Scope.PARAMETERS);
+        // Make a copy to avoid clashing with parent validation
+        final HashMap<String, Object> verifyParams = new HashMap<>(parameters);
+        // Check if validation is rest-related
+        final boolean isRest = verifyParams.entrySet().stream().anyMatch(e -> e.getKey().startsWith("rest."));
+
+        if (isRest) {
+            String httpUri = getOption(verifyParams, "rest.host", String.class).orElse(null);
+            String path = getOption(verifyParams, "rest.path", String.class).map(FileUtil::stripLeadingSeparator).orElse(null);
+
+            if (ObjectHelper.isNotEmpty(httpUri) && ObjectHelper.isNotEmpty(path)) {
+                httpUri = httpUri + "/" + path;
+            }
+
+            verifyParams.put("httpURI", httpUri);
+
+            // Cleanup parameters map from rest related stuffs
+            verifyParams.entrySet().removeIf(e -> e.getKey().startsWith("rest."));
+        }
+
+        // Validate using the catalog
+        super.verifyParametersAgainstCatalog(builder, verifyParams);
+
+        return builder.build();
+    }
+
+    // *********************************
+    // Connectivity validation
+    // *********************************
+
+    @Override
+    protected Result verifyConnectivity(Map<String, Object> parameters) {
+        // Default is success
+        final ResultBuilder builder = ResultBuilder.withStatusAndScope(Result.Status.OK, Scope.CONNECTIVITY);
+        // Make a copy to avoid clashing with parent validation
+        final HashMap<String, Object> verifyParams = new HashMap<>(parameters);
+        // Check if validation is rest-related
+        final boolean isRest = verifyParams.entrySet().stream().anyMatch(e -> e.getKey().startsWith("rest."));
+
+        String httpUri;
+        Optional<String> httpMethod;
+
+        if (isRest) {
+            // We are doing rest endpoint validation but as today the endpoint
+            // can't do any param substitution so the validation is performed
+            // against the http uri
+            httpUri = getOption(verifyParams, "rest.host", String.class).orElse(null);
+            httpMethod = getOption(verifyParams, "rest.method", String.class);
+
+            String path = getOption(verifyParams, "rest.path", String.class).map(FileUtil::stripLeadingSeparator).orElse(null);
+            if (ObjectHelper.isNotEmpty(httpUri) && ObjectHelper.isNotEmpty(path)) {
+                httpUri = httpUri + "/" + path;
+            }
+
+            verifyParams.put("httpURI", httpUri);
+
+            // Cleanup parameters from rest related stuffs
+            verifyParams.entrySet().removeIf(e -> e.getKey().startsWith("rest."));
+        }
+
+        httpUri = getOption(verifyParams, "httpURI", String.class).orElse(null);
+        httpMethod = Optional.empty();
+
+        // Check whether the http uri is null or empty
+        if (ObjectHelper.isEmpty(httpUri)) {
+            builder.error(
+                ResultErrorBuilder.withMissingOption("httpURI")
+                    .detail("rest", isRest)
+                    .build()
+            );
+
+            // lack of httpURI is a blocking issue so no need to go further
+            // with the validation
+            return builder.build();
+        }
+
+        try {
+            final UndertowClientWrapper wrapper = new UndertowClientWrapper();
+            final ClientResponse response = wrapper.send(httpUri, httpMethod);
+
+            if (response != null) {
+                int code = response.getResponseCode();
+                if (code == 401) {
+                    // Unauthorized, add authUsername and authPassword to the list
+                    // of parameters in error
+                    builder.error(
+                        ResultErrorBuilder.withHttpCode(code)
+                            .description(response.getStatus())
+                            .build()
+                    );
+                } else if (code >= 300 && code < 400) {
+                    // redirect
+                    builder.error(
+                        ResultErrorBuilder.withHttpCode(code)
+                            .description(response.getStatus())
+                            .parameterKey("httpURI")
+                            .detail(
+                                VerificationError.HttpAttribute.HTTP_REDIRECT,
+                                () -> Optional.ofNullable(response.getResponseHeaders().get(Headers.LOCATION).getFirst()))
+                            .build()
+                    );
+                } else if (code >= 400) {
+                    // generic http error
+                    builder.error(
+                        ResultErrorBuilder.withHttpCode(code)
+                            .description(response.getStatus())
+                            .build()
+                    );
+                }
+            }
+
+        } catch (Exception e) {
+            builder.error(
+                ResultErrorBuilder.withException(e).build()
+            );
+        }
+
+        return builder.build();
+    }
+
+    // *********************************
+    // Helpers
+    // *********************************
+
+    private final class UndertowClientWrapper {
+        private final XnioWorker worker;
+        private final ByteBufferPool pool;
+        private UndertowClient client;
+
+        private UndertowClientWrapper() throws IOException, URISyntaxException {
+            this.worker = Xnio.getInstance().createWorker(OptionMap.EMPTY);
+            this.pool = new DefaultByteBufferPool(true, 17 * 1024);
+            this.client = UndertowClient.getInstance(getCamelContext().getApplicationContextClassLoader());
+        }
+
+        public ClientResponse send(String httpUri, Optional<String> httpMethod) throws Exception {
+            URI uri = new URI(UnsafeUriCharactersEncoder.encodeHttpURI(httpUri));
+            HttpString method = httpMethod.map(Methods::fromString).orElse(Methods.GET);
+
+            ClientRequest request = new ClientRequest();
+            request.setMethod(method);
+            request.setPath(URISupport.pathAndQueryOf(uri));
+
+            IoFuture<ClientConnection> connectFuture = client.connect(uri, worker, pool, OptionMap.EMPTY);
+            UndertowClientResponseFuture responseFuture = new UndertowClientResponseFuture();
+
+            connectFuture.get().sendRequest(request, responseFuture);
+
+            // We should set a timeout
+            return responseFuture.get().getResponse();
+        }
+    }
+
+    private static final class UndertowClientResponseFuture extends AbstractIoFuture<ClientExchange> implements ClientCallback<ClientExchange> {
+        @Override
+        public void completed(ClientExchange result) {
+            result.setResponseListener(new ClientCallback<ClientExchange>() {
+                @Override
+                public void completed(ClientExchange result) {
+                    setResult(result);
+                }
+                @Override
+                public void failed(IOException e) {
+                    setException(e);
+                }
+            });
+        }
+
+        @Override
+        public void failed(IOException e) {
+            setException(e);
+        }
+    }
+}

http://git-wip-us.apache.org/repos/asf/camel/blob/1c6cf329/components/camel-undertow/src/test/java/org/apache/camel/component/undertow/UndertowComponentVerifierTest.java
----------------------------------------------------------------------
diff --git a/components/camel-undertow/src/test/java/org/apache/camel/component/undertow/UndertowComponentVerifierTest.java b/components/camel-undertow/src/test/java/org/apache/camel/component/undertow/UndertowComponentVerifierTest.java
new file mode 100644
index 0000000..4dc3586
--- /dev/null
+++ b/components/camel-undertow/src/test/java/org/apache/camel/component/undertow/UndertowComponentVerifierTest.java
@@ -0,0 +1,103 @@
+/**
+ * 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.undertow;
+
+import java.nio.channels.UnresolvedAddressException;
+import java.util.HashMap;
+import java.util.Map;
+
+import org.apache.camel.ComponentVerifier;
+import org.apache.camel.builder.RouteBuilder;
+import org.junit.Assert;
+import org.junit.Test;
+
+public class UndertowComponentVerifierTest extends BaseUndertowTest {
+    @Test
+    public void testParameters() throws Exception {
+        UndertowComponent component = context().getComponent("undertow", UndertowComponent.class);
+        ComponentVerifier verifier = component.getVerifier();
+
+        Map<String, Object> parameters = new HashMap<>();
+        parameters.put("httpURI", "http://localhost:" + getPort());
+        parameters.put("tcpNoDelay", "true");
+
+        ComponentVerifier.Result result = verifier.verify(ComponentVerifier.Scope.PARAMETERS, parameters);
+
+        Assert.assertEquals(ComponentVerifier.Result.Status.OK, result.getStatus());
+    }
+
+    @Test
+    public void testMissingParameters() throws Exception {
+        UndertowComponent component = context.getComponent("undertow", UndertowComponent.class);
+        ComponentVerifier verifier = component.getVerifier();
+
+        Map<String, Object> parameters = new HashMap<>();
+        parameters.put("tcpNoDelay", "true");
+
+        ComponentVerifier.Result result = verifier.verify(ComponentVerifier.Scope.PARAMETERS, parameters);
+
+        Assert.assertEquals(ComponentVerifier.Result.Status.ERROR, result.getStatus());
+        Assert.assertEquals(1, result.getErrors().size());
+        Assert.assertEquals(ComponentVerifier.VerificationError.StandardCode.MISSING_PARAMETER, result.getErrors().get(0).getCode());
+        Assert.assertEquals(1, result.getErrors().get(0).getParameterKeys().size());
+        Assert.assertTrue(result.getErrors().get(0).getParameterKeys().contains("httpURI"));
+    }
+
+    @Test
+    public void testConnectivity() throws Exception {
+        UndertowComponent component = context().getComponent("undertow", UndertowComponent.class);
+        ComponentVerifier verifier = component.getVerifier();
+
+        Map<String, Object> parameters = new HashMap<>();
+        parameters.put("httpURI", "http://localhost:" + getPort());
+        parameters.put("tcpNoDelay", "true");
+
+        ComponentVerifier.Result result = verifier.verify(ComponentVerifier.Scope.CONNECTIVITY, parameters);
+
+        Assert.assertEquals(ComponentVerifier.Result.Status.OK, result.getStatus());
+    }
+
+    @Test
+    public void testConnectivityError() throws Exception {
+        UndertowComponent component = context().getComponent("undertow", UndertowComponent.class);
+        ComponentVerifier verifier = component.getVerifier();
+
+        Map<String, Object> parameters = new HashMap<>();
+        parameters.put("httpURI", "http://no-host:" + getPort());
+
+        ComponentVerifier.Result result = verifier.verify(ComponentVerifier.Scope.CONNECTIVITY, parameters);
+
+        Assert.assertEquals(ComponentVerifier.Result.Status.ERROR, result.getStatus());
+        Assert.assertEquals(1, result.getErrors().size());
+
+        ComponentVerifier.VerificationError error = result.getErrors().get(0);
+
+        Assert.assertEquals(ComponentVerifier.VerificationError.StandardCode.EXCEPTION, error.getCode());
+        Assert.assertTrue(error.getDetail(ComponentVerifier.VerificationError.ExceptionAttribute.EXCEPTION_INSTANCE) instanceof UnresolvedAddressException);
+    }
+
+    @Override
+    protected RouteBuilder createRouteBuilder() throws Exception {
+        return new RouteBuilder() {
+            @Override
+            public void configure() throws Exception {
+                from("undertow:http://localhost:{{port}}")
+                    .process(e -> e.getOut().setBody("ok"));
+            }
+        };
+    }
+}

http://git-wip-us.apache.org/repos/asf/camel/blob/1c6cf329/components/camel-undertow/src/test/java/org/apache/camel/component/undertow/rest/RestUndertowComponentVerifierTest.java
----------------------------------------------------------------------
diff --git a/components/camel-undertow/src/test/java/org/apache/camel/component/undertow/rest/RestUndertowComponentVerifierTest.java b/components/camel-undertow/src/test/java/org/apache/camel/component/undertow/rest/RestUndertowComponentVerifierTest.java
new file mode 100644
index 0000000..8108134
--- /dev/null
+++ b/components/camel-undertow/src/test/java/org/apache/camel/component/undertow/rest/RestUndertowComponentVerifierTest.java
@@ -0,0 +1,126 @@
+/**
+ * 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.undertow.rest;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import org.apache.camel.ComponentVerifier;
+import org.apache.camel.builder.RouteBuilder;
+import org.apache.camel.component.rest.RestComponent;
+import org.apache.camel.component.undertow.BaseUndertowTest;
+import org.junit.Assert;
+import org.junit.Test;
+
+public class RestUndertowComponentVerifierTest extends BaseUndertowTest {
+    @Test
+    public void testParameters() throws Exception {
+        RestComponent component = context().getComponent("rest", RestComponent.class);
+        ComponentVerifier verifier = component.getVerifier();
+
+        Map<String, Object> parameters = new HashMap<>();
+        parameters.put("componentName", "undertow");
+        parameters.put("host", "http://localhost:" + getPort());
+        parameters.put("path", "verify");
+        parameters.put("method", "get");
+
+        ComponentVerifier.Result result = verifier.verify(ComponentVerifier.Scope.PARAMETERS, parameters);
+
+        Assert.assertEquals(ComponentVerifier.Result.Status.OK, result.getStatus());
+    }
+
+    @Test
+    public void testMissingRestParameters() throws Exception {
+        RestComponent component = context.getComponent("rest", RestComponent.class);
+        ComponentVerifier verifier = component.getVerifier();
+
+        Map<String, Object> parameters = new HashMap<>();
+        parameters.put("componentName", "undertow");
+        parameters.put("host", "http://localhost:" + getPort());
+        parameters.put("path", "verify");
+
+        // This parameter does not belong to the rest component and validation
+        // is delegated to the transport component
+        parameters.put("tcpNoDelay", true);
+
+        ComponentVerifier.Result result = verifier.verify(ComponentVerifier.Scope.PARAMETERS, parameters);
+
+        Assert.assertEquals(ComponentVerifier.Result.Status.ERROR, result.getStatus());
+        Assert.assertEquals(1, result.getErrors().size());
+        Assert.assertEquals(ComponentVerifier.VerificationError.StandardCode.MISSING_PARAMETER, result.getErrors().get(0).getCode());
+        Assert.assertEquals(1, result.getErrors().get(0).getParameterKeys().size());
+        Assert.assertTrue(result.getErrors().get(0).getParameterKeys().contains("method"));
+    }
+
+    @Test
+    public void testWrongComponentParameters() throws Exception {
+        RestComponent component = context.getComponent("rest", RestComponent.class);
+        ComponentVerifier verifier = component.getVerifier();
+
+        Map<String, Object> parameters = new HashMap<>();
+        parameters.put("componentName", "undertow");
+        parameters.put("host", "http://localhost:" + getPort());
+        parameters.put("path", "verify");
+        parameters.put("method", "get");
+
+        // This parameter does not belong to the rest component and validation
+        // is delegated to the transport component
+        parameters.put("nonExistingOption", true);
+
+        ComponentVerifier.Result result = verifier.verify(ComponentVerifier.Scope.PARAMETERS, parameters);
+
+        Assert.assertEquals(ComponentVerifier.Result.Status.ERROR, result.getStatus());
+        Assert.assertEquals(1, result.getErrors().size());
+        Assert.assertEquals(ComponentVerifier.VerificationError.StandardCode.UNKNOWN_PARAMETER, result.getErrors().get(0).getCode());
+        Assert.assertEquals(1, result.getErrors().get(0).getParameterKeys().size());
+        Assert.assertTrue(result.getErrors().get(0).getParameterKeys().contains("nonExistingOption"));
+    }
+
+    @Test
+    public void testConnectivity() throws Exception {
+        RestComponent component = context().getComponent("rest", RestComponent.class);
+        ComponentVerifier verifier = component.getVerifier();
+
+        Map<String, Object> parameters = new HashMap<>();
+        parameters.put("componentName", "undertow");
+        parameters.put("host", "http://localhost:" + getPort());
+        parameters.put("path", "verify");
+        parameters.put("method", "get");
+
+        ComponentVerifier.Result result = verifier.verify(ComponentVerifier.Scope.CONNECTIVITY, parameters);
+
+        Assert.assertEquals(ComponentVerifier.Result.Status.OK, result.getStatus());
+    }
+
+    @Override
+    protected RouteBuilder createRouteBuilder() throws Exception {
+        return new RouteBuilder() {
+            @Override
+            public void configure() throws Exception {
+                restConfiguration()
+                    .component("undertow")
+                    .host("localhost")
+                    .port(getPort());
+
+                rest("/")
+                    .get("/verify")
+                    .route()
+                        .process(e -> e.getOut().setBody("ok"));
+            }
+        };
+    }
+}


Mime
View raw message