You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@camel.apache.org by da...@apache.org on 2017/03/08 17:58:38 UTC

[2/4] camel git commit: CAMEL-10959: add RuntimeCamelCatalog to camel-core so we can reuse more catalog logic at runtime and have camel-catalog for tooling that has the complete catalog content. We need the runtime for component health check and more in

http://git-wip-us.apache.org/repos/asf/camel/blob/f5848e39/camel-core/src/test/java/org/apache/camel/catalog/RuntimeCamelCatalogTest.java
----------------------------------------------------------------------
diff --git a/camel-core/src/test/java/org/apache/camel/catalog/RuntimeCamelCatalogTest.java b/camel-core/src/test/java/org/apache/camel/catalog/RuntimeCamelCatalogTest.java
new file mode 100644
index 0000000..64637d6
--- /dev/null
+++ b/camel-core/src/test/java/org/apache/camel/catalog/RuntimeCamelCatalogTest.java
@@ -0,0 +1,392 @@
+/**
+ * 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.catalog;
+
+import java.io.FileInputStream;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import com.fasterxml.jackson.databind.JsonNode;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import org.apache.camel.impl.DefaultCamelContext;
+import org.junit.BeforeClass;
+import org.junit.Test;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import static org.apache.camel.catalog.CatalogHelper.loadText;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+
+public class RuntimeCamelCatalogTest {
+
+    static RuntimeCamelCatalog catalog;
+
+    private static final Logger LOG = LoggerFactory.getLogger(RuntimeCamelCatalogTest.class);
+
+    @BeforeClass
+    public static void createCamelCatalog() {
+        catalog = new DefaultRuntimeCamelCatalog(new DefaultCamelContext());
+    }
+
+    @Test
+    public void testJsonSchema() throws Exception {
+        String schema = catalog.modelJSonSchema("aggregate");
+        assertNotNull(schema);
+
+        // lets make it possible to find bean/method using both names
+        schema = catalog.modelJSonSchema("method");
+        assertNotNull(schema);
+        schema = catalog.modelJSonSchema("bean");
+        assertNotNull(schema);
+    }
+
+    @Test
+    public void testAsEndpointUriMapFile() throws Exception {
+        Map<String, String> map = new HashMap<String, String>();
+        map.put("directoryName", "src/data/inbox");
+        map.put("noop", "true");
+        map.put("delay", "5000");
+
+        String uri = catalog.asEndpointUri("file", map, true);
+        assertEquals("file:src/data/inbox?delay=5000&noop=true", uri);
+
+        String uri2 = catalog.asEndpointUriXml("file", map, true);
+        assertEquals("file:src/data/inbox?delay=5000&amp;noop=true", uri2);
+    }
+
+    @Test
+    public void testAsEndpointUriTimer() throws Exception {
+        Map<String, String> map = new HashMap<String, String>();
+        map.put("timerName", "foo");
+        map.put("period", "5000");
+
+        String uri = catalog.asEndpointUri("timer", map, true);
+        assertEquals("timer:foo?period=5000", uri);
+    }
+
+    @Test
+    public void testAsEndpointUriPropertiesPlaceholders() throws Exception {
+        Map<String, String> map = new HashMap<String, String>();
+        map.put("timerName", "foo");
+        map.put("period", "{{howoften}}");
+        map.put("repeatCount", "5");
+
+        String uri = catalog.asEndpointUri("timer", map, true);
+        assertEquals("timer:foo?period=%7B%7Bhowoften%7D%7D&repeatCount=5", uri);
+
+        uri = catalog.asEndpointUri("timer", map, false);
+        assertEquals("timer:foo?period={{howoften}}&repeatCount=5", uri);
+    }
+
+    @Test
+    public void testAsEndpointUriBeanLookup() throws Exception {
+        Map<String, String> map = new HashMap<String, String>();
+        map.put("resourceUri", "foo.xslt");
+        map.put("converter", "#myConverter");
+
+        String uri = catalog.asEndpointUri("xslt", map, true);
+        assertEquals("xslt:foo.xslt?converter=%23myConverter", uri);
+
+        uri = catalog.asEndpointUri("xslt", map, false);
+        assertEquals("xslt:foo.xslt?converter=#myConverter", uri);
+    }
+
+    @Test
+    public void testEndpointPropertiesPlaceholders() throws Exception {
+        Map<String, String> map = catalog.endpointProperties("timer:foo?period={{howoften}}&repeatCount=5");
+        assertNotNull(map);
+        assertEquals(3, map.size());
+
+        assertEquals("foo", map.get("timerName"));
+        assertEquals("{{howoften}}", map.get("period"));
+        assertEquals("5", map.get("repeatCount"));
+    }
+
+    @Test
+    public void testAsEndpointUriLog() throws Exception {
+        Map<String, String> map = new HashMap<String, String>();
+        map.put("loggerName", "foo");
+        map.put("loggerLevel", "WARN");
+        map.put("multiline", "true");
+        map.put("showAll", "true");
+        map.put("showBody", "false");
+        map.put("showBodyType", "false");
+        map.put("showExchangePattern", "false");
+        map.put("style", "Tab");
+
+        assertEquals("log:foo?loggerLevel=WARN&multiline=true&showAll=true&style=Tab", catalog.asEndpointUri("log", map, false));
+    }
+
+    @Test
+    public void testAsEndpointUriLogShort() throws Exception {
+        Map<String, String> map = new HashMap<String, String>();
+        map.put("loggerName", "foo");
+        map.put("loggerLevel", "DEBUG");
+
+        assertEquals("log:foo?loggerLevel=DEBUG", catalog.asEndpointUri("log", map, false));
+    }
+
+    @Test
+    public void testAsEndpointUriWithplaceholder() throws Exception {
+        Map<String, String> map = new HashMap<String, String>();
+        map.put("name", "foo");
+        map.put("blockWhenFull", "{{block}}");
+        assertEquals("seda:foo?blockWhenFull={{block}}", catalog.asEndpointUri("seda", map, false));
+    }
+
+    @Test
+    public void testEndpointPropertiesSedaRequired() throws Exception {
+        Map<String, String> map = catalog.endpointProperties("seda:foo");
+        assertNotNull(map);
+        assertEquals(1, map.size());
+
+        assertEquals("foo", map.get("name"));
+
+        map = catalog.endpointProperties("seda:foo?blockWhenFull=true");
+        assertNotNull(map);
+        assertEquals(2, map.size());
+
+        assertEquals("foo", map.get("name"));
+        assertEquals("true", map.get("blockWhenFull"));
+    }
+
+    @Test
+    public void validateProperties() throws Exception {
+        // valid
+        EndpointValidationResult result = catalog.validateEndpointProperties("log:mylog");
+        assertTrue(result.isSuccess());
+
+        // unknown
+        result = catalog.validateEndpointProperties("log:mylog?level=WARN&foo=bar");
+        assertFalse(result.isSuccess());
+        assertTrue(result.getUnknown().contains("foo"));
+        assertEquals(1, result.getNumberOfErrors());
+
+        // enum
+        result = catalog.validateEndpointProperties("seda:foo?waitForTaskToComplete=blah");
+        assertFalse(result.isSuccess());
+        assertEquals("blah", result.getInvalidEnum().get("waitForTaskToComplete"));
+        assertEquals(1, result.getNumberOfErrors());
+
+        // reference okay
+        result = catalog.validateEndpointProperties("seda:foo?queue=#queue");
+        assertTrue(result.isSuccess());
+        assertEquals(0, result.getNumberOfErrors());
+
+        // unknown component
+        result = catalog.validateEndpointProperties("foo:bar?me=you");
+        assertFalse(result.isSuccess());
+        assertTrue(result.getUnknownComponent().equals("foo"));
+        assertEquals(1, result.getNumberOfErrors());
+
+        // invalid boolean but default value
+        result = catalog.validateEndpointProperties("log:output?showAll=ggg");
+        assertFalse(result.isSuccess());
+        assertEquals("ggg", result.getInvalidBoolean().get("showAll"));
+        assertEquals(1, result.getNumberOfErrors());
+
+        // dataset
+        result = catalog.validateEndpointProperties("dataset:foo?minRate=50");
+        assertTrue(result.isSuccess());
+
+        // time pattern
+        result = catalog.validateEndpointProperties("timer://foo?fixedRate=true&delay=0&period=2s");
+        assertTrue(result.isSuccess());
+
+        // reference lookup
+        result = catalog.validateEndpointProperties("timer://foo?fixedRate=#fixed&delay=#myDelay");
+        assertTrue(result.isSuccess());
+
+        // optional consumer. prefix
+        result = catalog.validateEndpointProperties("file:inbox?consumer.delay=5000&consumer.greedy=true");
+        assertTrue(result.isSuccess());
+
+        // optional without consumer. prefix
+        result = catalog.validateEndpointProperties("file:inbox?delay=5000&greedy=true");
+        assertTrue(result.isSuccess());
+
+        // mixed optional without consumer. prefix
+        result = catalog.validateEndpointProperties("file:inbox?delay=5000&consumer.greedy=true");
+        assertTrue(result.isSuccess());
+
+        // prefix
+        result = catalog.validateEndpointProperties("file:inbox?delay=5000&scheduler.foo=123&scheduler.bar=456");
+        assertTrue(result.isSuccess());
+
+        // stub
+        result = catalog.validateEndpointProperties("stub:foo?me=123&you=456");
+        assertTrue(result.isSuccess());
+
+        // lenient on
+        result = catalog.validateEndpointProperties("dataformat:string:marshal?foo=bar");
+        assertTrue(result.isSuccess());
+
+        // lenient off
+        result = catalog.validateEndpointProperties("dataformat:string:marshal?foo=bar", true);
+        assertFalse(result.isSuccess());
+        assertTrue(result.getUnknown().contains("foo"));
+
+        // data format
+        result = catalog.validateEndpointProperties("dataformat:string:marshal?charset=utf-8", true);
+        assertTrue(result.isSuccess());
+
+        // incapable to parse
+        result = catalog.validateEndpointProperties("{{getFtpUrl}}?recursive=true");
+        assertFalse(result.isSuccess());
+        assertTrue(result.getIncapable() != null);
+    }
+
+    @Test
+    public void validatePropertiesSummary() throws Exception {
+        EndpointValidationResult result = catalog.validateEndpointProperties("yammer:MESSAGES?blah=yada&accessToken=aaa&consumerKey=&useJson=no&initialDelay=five&pollStrategy=myStrategy");
+        assertFalse(result.isSuccess());
+        String reason = result.summaryErrorMessage(true);
+        LOG.info(reason);
+
+        result = catalog.validateEndpointProperties("jms:unknown:myqueue");
+        assertFalse(result.isSuccess());
+        reason = result.summaryErrorMessage(false);
+        LOG.info(reason);
+    }
+
+    @Test
+    public void validateTimePattern() throws Exception {
+        assertTrue(catalog.validateTimePattern("0"));
+        assertTrue(catalog.validateTimePattern("500"));
+        assertTrue(catalog.validateTimePattern("10000"));
+        assertTrue(catalog.validateTimePattern("5s"));
+        assertTrue(catalog.validateTimePattern("5sec"));
+        assertTrue(catalog.validateTimePattern("5secs"));
+        assertTrue(catalog.validateTimePattern("3m"));
+        assertTrue(catalog.validateTimePattern("3min"));
+        assertTrue(catalog.validateTimePattern("3minutes"));
+        assertTrue(catalog.validateTimePattern("5m15s"));
+        assertTrue(catalog.validateTimePattern("1h"));
+        assertTrue(catalog.validateTimePattern("1hour"));
+        assertTrue(catalog.validateTimePattern("2hours"));
+
+        assertFalse(catalog.validateTimePattern("bla"));
+        assertFalse(catalog.validateTimePattern("2year"));
+        assertFalse(catalog.validateTimePattern("60darn"));
+    }
+
+    @Test
+    public void testEndpointComponentName() throws Exception {
+        String name = catalog.endpointComponentName("jms:queue:foo");
+        assertEquals("jms", name);
+    }
+
+    @Test
+    public void testSimpleExpression() throws Exception {
+        SimpleValidationResult result = catalog.validateSimpleExpression(null, "${body}");
+        assertTrue(result.isSuccess());
+        assertEquals("${body}", result.getSimple());
+
+        result = catalog.validateSimpleExpression(null, "${body");
+        assertFalse(result.isSuccess());
+        assertEquals("${body", result.getSimple());
+        LOG.info(result.getError());
+        assertTrue(result.getError().startsWith("expected symbol functionEnd but was eol at location 5"));
+        assertEquals("expected symbol functionEnd but was eol", result.getShortError());
+        assertEquals(5, result.getIndex());
+    }
+
+    @Test
+    public void testSimplePredicate() throws Exception {
+        SimpleValidationResult result = catalog.validateSimplePredicate(null, "${body} == 'abc'");
+        assertTrue(result.isSuccess());
+        assertEquals("${body} == 'abc'", result.getSimple());
+
+        result = catalog.validateSimplePredicate(null, "${body} > ${header.size");
+        assertFalse(result.isSuccess());
+        assertEquals("${body} > ${header.size", result.getSimple());
+        LOG.info(result.getError());
+        assertTrue(result.getError().startsWith("expected symbol functionEnd but was eol at location 22"));
+        assertEquals("expected symbol functionEnd but was eol", result.getShortError());
+        assertEquals(22, result.getIndex());
+    }
+
+    @Test
+    public void testSimplePredicatePlaceholder() throws Exception {
+        SimpleValidationResult result = catalog.validateSimplePredicate(null, "${body} contains '{{danger}}'");
+        assertTrue(result.isSuccess());
+        assertEquals("${body} contains '{{danger}}'", result.getSimple());
+
+        result = catalog.validateSimplePredicate(null, "${bdy} contains '{{danger}}'");
+        assertFalse(result.isSuccess());
+        assertEquals("${bdy} contains '{{danger}}'", result.getSimple());
+        LOG.info(result.getError());
+        assertTrue(result.getError().startsWith("Unknown function: bdy at location 0"));
+        assertTrue(result.getError().contains("'{{danger}}'"));
+        assertEquals("Unknown function: bdy", result.getShortError());
+        assertEquals(0, result.getIndex());
+    }
+
+    @Test
+    public void testValidateLanguage() throws Exception {
+        LanguageValidationResult result = catalog.validateLanguageExpression(null, "simple", "${body}");
+        assertTrue(result.isSuccess());
+        assertEquals("${body}", result.getText());
+
+        result = catalog.validateLanguageExpression(null, "header", "foo");
+        assertTrue(result.isSuccess());
+        assertEquals("foo", result.getText());
+
+        result = catalog.validateLanguagePredicate(null, "simple", "${body} > 10");
+        assertTrue(result.isSuccess());
+        assertEquals("${body} > 10", result.getText());
+
+        result = catalog.validateLanguagePredicate(null, "header", "bar");
+        assertTrue(result.isSuccess());
+        assertEquals("bar", result.getText());
+
+        result = catalog.validateLanguagePredicate(null, "foobar", "bar");
+        assertFalse(result.isSuccess());
+        assertEquals("Unknown language foobar", result.getError());
+    }
+
+    @Test
+    public void testValidateEndpointConsumerOnly() throws Exception {
+        String uri = "file:inbox?bufferSize=4096&readLock=changed&delete=true";
+        EndpointValidationResult result = catalog.validateEndpointProperties(uri, false, true, false);
+        assertTrue(result.isSuccess());
+
+        uri = "file:inbox?bufferSize=4096&readLock=changed&delete=true&fileExist=Append";
+        result = catalog.validateEndpointProperties(uri, false, true, false);
+        assertFalse(result.isSuccess());
+
+        assertEquals("fileExist", result.getNotConsumerOnly().iterator().next());
+    }
+
+    @Test
+    public void testValidateEndpointProducerOnly() throws Exception {
+        String uri = "file:outbox?bufferSize=4096&fileExist=Append";
+        EndpointValidationResult result = catalog.validateEndpointProperties(uri, false, false, true);
+        assertTrue(result.isSuccess());
+
+        uri = "file:outbox?bufferSize=4096&fileExist=Append&delete=true";
+        result = catalog.validateEndpointProperties(uri, false, false, true);
+        assertFalse(result.isSuccess());
+
+        assertEquals("delete", result.getNotProducerOnly().iterator().next());
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/camel/blob/f5848e39/platforms/camel-catalog/pom.xml
----------------------------------------------------------------------
diff --git a/platforms/camel-catalog/pom.xml b/platforms/camel-catalog/pom.xml
index 2f096c4..2f2f56f 100644
--- a/platforms/camel-catalog/pom.xml
+++ b/platforms/camel-catalog/pom.xml
@@ -1,5 +1,6 @@
 <?xml version="1.0" encoding="UTF-8"?>
-<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
   <!--
     Licensed to the Apache Software Foundation (ASF) under one or more
     contributor license agreements.  See the NOTICE file distributed with
@@ -29,6 +30,11 @@
   <name>Camel :: Platforms :: Camel Catalog</name>
   <description>Camel Catalog</description>
 
+  <!-- the src/main/java folder is empty and that is on purpose, as the source code is in tooling/camel-catalog-core
+       and is copied into this JAR at build time so we can reuse the same code in both camel-core and camel-catalog.
+       For camel-core it allows us to use the catalog APIs at runtime, and for camel-catalog it is a 100% standalone
+       JAR with all the json files embedded for tooling to use. -->
+
   <dependencies>
 
     <!-- no dependency -->
@@ -60,7 +66,7 @@
       <artifactId>junit</artifactId>
       <scope>test</scope>
     </dependency>
-    
+
     <!-- logging -->
     <dependency>
       <groupId>org.apache.logging.log4j</groupId>
@@ -83,6 +89,45 @@
   <build>
     <plugins>
 
+      <!-- copy catalog source code from camel-core -->
+      <plugin>
+        <groupId>org.apache.maven.plugins</groupId>
+        <artifactId>maven-resources-plugin</artifactId>
+        <version>${maven-resources-plugin-version}</version>
+        <executions>
+          <execution>
+            <id>copy-resources</id>
+            <phase>process-sources</phase>
+            <goals>
+              <goal>copy-resources</goal>
+            </goals>
+            <configuration>
+              <outputDirectory>src/main/java/org/apache/camel/catalog</outputDirectory>
+              <overwrite>true</overwrite>
+              <resources>
+                <resource>
+                  <directory>../../camel-core/src/main/java/org/apache/camel/catalog</directory>
+                  <includes>
+                    <include>AbstractCamelCatalog.java</include>
+                    <include>CatalogHelper.java</include>
+                    <include>CollectionStringBuffer.java</include>
+                    <include>EndpointValidationResult.java</include>
+                    <include>JSonSchemaHelper.java</include>
+                    <include>JSonSchemaResolver.java</include>
+                    <include>LanguageValidationResult.java</include>
+                    <include>SimpleValidationResult.java</include>
+                    <include>SuggestionStrategy.java</include>
+                    <include>TimePatternConverter.java</include>
+                    <include>UnsafeUriCharactersEncoder.java</include>
+                    <include>URISupport.java</include>
+                  </includes>
+                </resource>
+              </resources>
+            </configuration>
+          </execution>
+        </executions>
+      </plugin>
+
       <!-- generate and include all components in the catalog -->
       <plugin>
         <groupId>org.apache.camel</groupId>

http://git-wip-us.apache.org/repos/asf/camel/blob/f5848e39/platforms/camel-catalog/src/main/java/org/apache/camel/catalog/AbstractCamelCatalog.java
----------------------------------------------------------------------
diff --git a/platforms/camel-catalog/src/main/java/org/apache/camel/catalog/AbstractCamelCatalog.java b/platforms/camel-catalog/src/main/java/org/apache/camel/catalog/AbstractCamelCatalog.java
new file mode 100644
index 0000000..3295ca9
--- /dev/null
+++ b/platforms/camel-catalog/src/main/java/org/apache/camel/catalog/AbstractCamelCatalog.java
@@ -0,0 +1,1029 @@
+/**
+ * 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.catalog;
+
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.LinkedHashMap;
+import java.util.LinkedHashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.TreeMap;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+import static org.apache.camel.catalog.CatalogHelper.after;
+import static org.apache.camel.catalog.JSonSchemaHelper.getNames;
+import static org.apache.camel.catalog.JSonSchemaHelper.getPropertyDefaultValue;
+import static org.apache.camel.catalog.JSonSchemaHelper.getPropertyEnum;
+import static org.apache.camel.catalog.JSonSchemaHelper.getPropertyKind;
+import static org.apache.camel.catalog.JSonSchemaHelper.getPropertyNameFromNameWithPrefix;
+import static org.apache.camel.catalog.JSonSchemaHelper.getPropertyPrefix;
+import static org.apache.camel.catalog.JSonSchemaHelper.getRow;
+import static org.apache.camel.catalog.JSonSchemaHelper.isComponentConsumerOnly;
+import static org.apache.camel.catalog.JSonSchemaHelper.isComponentLenientProperties;
+import static org.apache.camel.catalog.JSonSchemaHelper.isComponentProducerOnly;
+import static org.apache.camel.catalog.JSonSchemaHelper.isPropertyBoolean;
+import static org.apache.camel.catalog.JSonSchemaHelper.isPropertyConsumerOnly;
+import static org.apache.camel.catalog.JSonSchemaHelper.isPropertyInteger;
+import static org.apache.camel.catalog.JSonSchemaHelper.isPropertyMultiValue;
+import static org.apache.camel.catalog.JSonSchemaHelper.isPropertyNumber;
+import static org.apache.camel.catalog.JSonSchemaHelper.isPropertyObject;
+import static org.apache.camel.catalog.JSonSchemaHelper.isPropertyProducerOnly;
+import static org.apache.camel.catalog.JSonSchemaHelper.isPropertyRequired;
+import static org.apache.camel.catalog.JSonSchemaHelper.stripOptionalPrefixFromName;
+import static org.apache.camel.catalog.URISupport.createQueryString;
+import static org.apache.camel.catalog.URISupport.isEmpty;
+import static org.apache.camel.catalog.URISupport.normalizeUri;
+import static org.apache.camel.catalog.URISupport.stripQuery;
+
+/**
+ * Base class for both the runtime RuntimeCamelCatalog from camel-core and the complete CamelCatalog from camel-catalog.
+ */
+public abstract class AbstractCamelCatalog {
+
+    // CHECKSTYLE:OFF
+
+    private static final Pattern SYNTAX_PATTERN = Pattern.compile("(\\w+)");
+
+    private SuggestionStrategy suggestionStrategy;
+    private JSonSchemaResolver jsonSchemaResolver;
+
+    public SuggestionStrategy getSuggestionStrategy() {
+        return suggestionStrategy;
+    }
+
+    public void setSuggestionStrategy(SuggestionStrategy suggestionStrategy) {
+        this.suggestionStrategy = suggestionStrategy;
+    }
+
+    public JSonSchemaResolver getJSonSchemaResolver() {
+        return jsonSchemaResolver;
+    }
+
+    public void setJSonSchemaResolver(JSonSchemaResolver resolver) {
+        this.jsonSchemaResolver = resolver;
+    }
+
+    public boolean validateTimePattern(String pattern) {
+        return validateInteger(pattern);
+    }
+
+    public EndpointValidationResult validateEndpointProperties(String uri) {
+        return validateEndpointProperties(uri, false, false, false);
+    }
+
+    public EndpointValidationResult validateEndpointProperties(String uri, boolean ignoreLenientProperties) {
+        return validateEndpointProperties(uri, ignoreLenientProperties, false, false);
+    }
+
+    public EndpointValidationResult validateEndpointProperties(String uri, boolean ignoreLenientProperties, boolean consumerOnly, boolean producerOnly) {
+        EndpointValidationResult result = new EndpointValidationResult(uri);
+
+        Map<String, String> properties;
+        List<Map<String, String>> rows;
+        boolean lenientProperties;
+        String scheme;
+
+        try {
+            String json = null;
+
+            // parse the uri
+            URI u = normalizeUri(uri);
+            scheme = u.getScheme();
+
+            if (scheme != null) {
+                json = jsonSchemaResolver.getComponentJSonSchema(scheme);
+            }
+            if (json == null) {
+                // if the uri starts with a placeholder then we are also incapable of parsing it as we wasn't able to resolve the component name
+                if (uri.startsWith("{{")) {
+                    result.addIncapable(uri);
+                } else if (scheme != null) {
+                    result.addUnknownComponent(scheme);
+                } else {
+                    result.addUnknownComponent(uri);
+                }
+                return result;
+            }
+
+            rows = JSonSchemaHelper.parseJsonSchema("component", json, false);
+
+            // is the component capable of both consumer and producer?
+            boolean canConsumeAndProduce = false;
+            if (!isComponentConsumerOnly(rows) && !isComponentProducerOnly(rows)) {
+                canConsumeAndProduce = true;
+            }
+
+            if (canConsumeAndProduce && consumerOnly) {
+                // lenient properties is not support in consumer only mode if the component can do both of them
+                lenientProperties = false;
+            } else {
+                // only enable lenient properties if we should not ignore
+                lenientProperties = !ignoreLenientProperties && isComponentLenientProperties(rows);
+            }
+            rows = JSonSchemaHelper.parseJsonSchema("properties", json, true);
+            properties = endpointProperties(uri);
+        } catch (URISyntaxException e) {
+            if (uri.startsWith("{{")) {
+                // if the uri starts with a placeholder then we are also incapable of parsing it as we wasn't able to resolve the component name
+                result.addIncapable(uri);
+            } else {
+                result.addSyntaxError(e.getMessage());
+            }
+
+            return result;
+        }
+
+        // the dataformat component refers to a data format so lets add the properties for the selected
+        // data format to the list of rows
+        if ("dataformat".equals(scheme)) {
+            String dfName = properties.get("name");
+            if (dfName != null) {
+                String dfJson = jsonSchemaResolver.getDataFormatJSonSchema(dfName);
+                List<Map<String, String>> dfRows = JSonSchemaHelper.parseJsonSchema("properties", dfJson, true);
+                if (dfRows != null && !dfRows.isEmpty()) {
+                    rows.addAll(dfRows);
+                }
+            }
+        }
+
+        for (Map.Entry<String, String> property : properties.entrySet()) {
+            String value = property.getValue();
+            String originalName = property.getKey();
+            String name = property.getKey();
+            // the name may be using an optional prefix, so lets strip that because the options
+            // in the schema are listed without the prefix
+            name = stripOptionalPrefixFromName(rows, name);
+            // the name may be using a prefix, so lets see if we can find the real property name
+            String propertyName = getPropertyNameFromNameWithPrefix(rows, name);
+            if (propertyName != null) {
+                name = propertyName;
+            }
+
+            String prefix = getPropertyPrefix(rows, name);
+            String kind = getPropertyKind(rows, name);
+            boolean namePlaceholder = name.startsWith("{{") && name.endsWith("}}");
+            boolean valuePlaceholder = value.startsWith("{{") || value.startsWith("${") || value.startsWith("$simple{");
+            boolean lookup = value.startsWith("#") && value.length() > 1;
+            // we cannot evaluate multi values as strict as the others, as we don't know their expected types
+            boolean mulitValue = prefix != null && originalName.startsWith(prefix) && isPropertyMultiValue(rows, name);
+
+            Map<String, String> row = getRow(rows, name);
+            if (row == null) {
+                // unknown option
+
+                // only add as error if the component is not lenient properties, or not stub component
+                // and the name is not a property placeholder for one or more values
+                if (!namePlaceholder && !"stub".equals(scheme)) {
+                    if (lenientProperties) {
+                        // as if we are lenient then the option is a dynamic extra option which we cannot validate
+                        result.addLenient(name);
+                    } else {
+                        // its unknown
+                        result.addUnknown(name);
+                        if (suggestionStrategy != null) {
+                            String[] suggestions = suggestionStrategy.suggestEndpointOptions(getNames(rows), name);
+                            if (suggestions != null) {
+                                result.addUnknownSuggestions(name, suggestions);
+                            }
+                        }
+                    }
+                }
+            } else {
+                if ("parameter".equals(kind)) {
+                    // consumer only or producer only mode for parameters
+                    if (consumerOnly) {
+                        boolean producer = isPropertyProducerOnly(rows, name);
+                        if (producer) {
+                            // the option is only for producer so you cannot use it in consumer mode
+                            result.addNotConsumerOnly(name);
+                        }
+                    } else if (producerOnly) {
+                        boolean consumer = isPropertyConsumerOnly(rows, name);
+                        if (consumer) {
+                            // the option is only for consumer so you cannot use it in producer mode
+                            result.addNotProducerOnly(name);
+                        }
+                    }
+                }
+
+                // default value
+                String defaultValue = getPropertyDefaultValue(rows, name);
+                if (defaultValue != null) {
+                    result.addDefaultValue(name, defaultValue);
+                }
+
+                // is required but the value is empty
+                boolean required = isPropertyRequired(rows, name);
+                if (required && isEmpty(value)) {
+                    result.addRequired(name);
+                }
+
+                // is enum but the value is not within the enum range
+                // but we can only check if the value is not a placeholder
+                String enums = getPropertyEnum(rows, name);
+                if (!mulitValue && !valuePlaceholder && !lookup && enums != null) {
+                    String[] choices = enums.split(",");
+                    boolean found = false;
+                    for (String s : choices) {
+                        if (value.equalsIgnoreCase(s)) {
+                            found = true;
+                            break;
+                        }
+                    }
+                    if (!found) {
+                        result.addInvalidEnum(name, value);
+                        result.addInvalidEnumChoices(name, choices);
+                        if (suggestionStrategy != null) {
+                            Set<String> names = new LinkedHashSet<>();
+                            names.addAll(Arrays.asList(choices));
+                            String[] suggestions = suggestionStrategy.suggestEndpointOptions(names, value);
+                            if (suggestions != null) {
+                                result.addInvalidEnumSuggestions(name, suggestions);
+                            }
+                        }
+
+                    }
+                }
+
+                // is reference lookup of bean (not applicable for @UriPath, enums, or multi-valued)
+                if (!mulitValue && enums == null && !"path".equals(kind) && isPropertyObject(rows, name)) {
+                    // must start with # and be at least 2 characters
+                    if (!value.startsWith("#") || value.length() <= 1) {
+                        result.addInvalidReference(name, value);
+                    }
+                }
+
+                // is boolean
+                if (!mulitValue && !valuePlaceholder && !lookup && isPropertyBoolean(rows, name)) {
+                    // value must be a boolean
+                    boolean bool = "true".equalsIgnoreCase(value) || "false".equalsIgnoreCase(value);
+                    if (!bool) {
+                        result.addInvalidBoolean(name, value);
+                    }
+                }
+
+                // is integer
+                if (!mulitValue && !valuePlaceholder && !lookup && isPropertyInteger(rows, name)) {
+                    // value must be an integer
+                    boolean valid = validateInteger(value);
+                    if (!valid) {
+                        result.addInvalidInteger(name, value);
+                    }
+                }
+
+                // is number
+                if (!mulitValue && !valuePlaceholder && !lookup && isPropertyNumber(rows, name)) {
+                    // value must be an number
+                    boolean valid = false;
+                    try {
+                        valid = !Double.valueOf(value).isNaN() || !Float.valueOf(value).isNaN();
+                    } catch (Exception e) {
+                        // ignore
+                    }
+                    if (!valid) {
+                        result.addInvalidNumber(name, value);
+                    }
+                }
+            }
+        }
+
+        // now check if all required values are there, and that a default value does not exists
+        for (Map<String, String> row : rows) {
+            String name = row.get("name");
+            boolean required = isPropertyRequired(rows, name);
+            if (required) {
+                String value = properties.get(name);
+                if (isEmpty(value)) {
+                    value = getPropertyDefaultValue(rows, name);
+                }
+                if (isEmpty(value)) {
+                    result.addRequired(name);
+                }
+            }
+        }
+
+        return result;
+    }
+
+    public Map<String, String> endpointProperties(String uri) throws URISyntaxException {
+        // need to normalize uri first
+        URI u = normalizeUri(uri);
+        String scheme = u.getScheme();
+
+        String json = jsonSchemaResolver.getComponentJSonSchema(scheme);
+        if (json == null) {
+            throw new IllegalArgumentException("Cannot find endpoint with scheme " + scheme);
+        }
+
+        // grab the syntax
+        String syntax = null;
+        String alternativeSyntax = null;
+        List<Map<String, String>> rows = JSonSchemaHelper.parseJsonSchema("component", json, false);
+        for (Map<String, String> row : rows) {
+            if (row.containsKey("syntax")) {
+                syntax = row.get("syntax");
+            }
+            if (row.containsKey("alternativeSyntax")) {
+                alternativeSyntax = row.get("alternativeSyntax");
+            }
+        }
+        if (syntax == null) {
+            throw new IllegalArgumentException("Endpoint with scheme " + scheme + " has no syntax defined in the json schema");
+        }
+
+        // only if we support alternative syntax, and the uri contains the username and password in the authority
+        // part of the uri, then we would need some special logic to capture that information and strip those
+        // details from the uri, so we can continue parsing the uri using the normal syntax
+        Map<String, String> userInfoOptions = new LinkedHashMap<String, String>();
+        if (alternativeSyntax != null && alternativeSyntax.contains("@")) {
+            // clip the scheme from the syntax
+            alternativeSyntax = after(alternativeSyntax, ":");
+            // trim so only userinfo
+            int idx = alternativeSyntax.indexOf("@");
+            String fields = alternativeSyntax.substring(0, idx);
+            String[] names = fields.split(":");
+
+            // grab authority part and grab username and/or password
+            String authority = u.getAuthority();
+            if (authority != null && authority.contains("@")) {
+                String username = null;
+                String password = null;
+
+                // grab unserinfo part before @
+                String userInfo = authority.substring(0, authority.indexOf("@"));
+                String[] parts = userInfo.split(":");
+                if (parts.length == 2) {
+                    username = parts[0];
+                    password = parts[1];
+                } else {
+                    // only username
+                    username = userInfo;
+                }
+
+                // remember the username and/or password which we add later to the options
+                if (names.length == 2) {
+                    userInfoOptions.put(names[0], username);
+                    if (password != null) {
+                        // password is optional
+                        userInfoOptions.put(names[1], password);
+                    }
+                }
+            }
+        }
+
+        // clip the scheme from the syntax
+        syntax = after(syntax, ":");
+        // clip the scheme from the uri
+        uri = after(uri, ":");
+        String uriPath = stripQuery(uri);
+
+        // strip user info from uri path
+        if (!userInfoOptions.isEmpty()) {
+            int idx = uriPath.indexOf('@');
+            if (idx > -1) {
+                uriPath = uriPath.substring(idx + 1);
+            }
+        }
+
+        // strip double slash in the start
+        if (uriPath != null && uriPath.startsWith("//")) {
+            uriPath = uriPath.substring(2);
+        }
+
+        // parse the syntax and find the names of each option
+        Matcher matcher = SYNTAX_PATTERN.matcher(syntax);
+        List<String> word = new ArrayList<String>();
+        while (matcher.find()) {
+            String s = matcher.group(1);
+            if (!scheme.equals(s)) {
+                word.add(s);
+            }
+        }
+        // parse the syntax and find each token between each option
+        String[] tokens = SYNTAX_PATTERN.split(syntax);
+
+        // find the position where each option start/end
+        List<String> word2 = new ArrayList<String>();
+        int prev = 0;
+        int prevPath = 0;
+
+        // special for activemq/jms where the enum for destinationType causes a token issue as it includes a colon
+        // for 'temp:queue' and 'temp:topic' values
+        if ("activemq".equals(scheme) || "jms".equals(scheme)) {
+            if (uriPath.startsWith("temp:")) {
+                prevPath = 5;
+            }
+        }
+
+        for (String token : tokens) {
+            if (token.isEmpty()) {
+                continue;
+            }
+
+            // special for some tokens where :// can be used also, eg http://foo
+            int idx = -1;
+            int len = 0;
+            if (":".equals(token)) {
+                idx = uriPath.indexOf("://", prevPath);
+                len = 3;
+            }
+            if (idx == -1) {
+                idx = uriPath.indexOf(token, prevPath);
+                len = token.length();
+            }
+
+            if (idx > 0) {
+                String option = uriPath.substring(prev, idx);
+                word2.add(option);
+                prev = idx + len;
+                prevPath = prev;
+            }
+        }
+        // special for last or if we did not add anyone
+        if (prev > 0 || word2.isEmpty()) {
+            String option = uriPath.substring(prev);
+            word2.add(option);
+        }
+
+        rows = JSonSchemaHelper.parseJsonSchema("properties", json, true);
+
+        boolean defaultValueAdded = false;
+
+        // now parse the uri to know which part isw what
+        Map<String, String> options = new LinkedHashMap<String, String>();
+
+        // include the username and password from the userinfo section
+        if (!userInfoOptions.isEmpty()) {
+            options.putAll(userInfoOptions);
+        }
+
+        // word contains the syntax path elements
+        Iterator<String> it = word2.iterator();
+        for (int i = 0; i < word.size(); i++) {
+            String key = word.get(i);
+
+            boolean allOptions = word.size() == word2.size();
+            boolean required = isPropertyRequired(rows, key);
+            String defaultValue = getPropertyDefaultValue(rows, key);
+
+            // we have all options so no problem
+            if (allOptions) {
+                String value = it.next();
+                options.put(key, value);
+            } else {
+                // we have a little problem as we do not not have all options
+                if (!required) {
+                    String value = null;
+
+                    boolean last = i == word.size() - 1;
+                    if (last) {
+                        // if its the last value then use it instead of the default value
+                        value = it.hasNext() ? it.next() : null;
+                        if (value != null) {
+                            options.put(key, value);
+                        } else {
+                            value = defaultValue;
+                        }
+                    }
+                    if (value != null) {
+                        options.put(key, value);
+                        defaultValueAdded = true;
+                    }
+                } else {
+                    String value = it.hasNext() ? it.next() : null;
+                    if (value != null) {
+                        options.put(key, value);
+                    }
+                }
+            }
+        }
+
+        Map<String, String> answer = new LinkedHashMap<String, String>();
+
+        // remove all options which are using default values and are not required
+        for (Map.Entry<String, String> entry : options.entrySet()) {
+            String key = entry.getKey();
+            String value = entry.getValue();
+
+            if (defaultValueAdded) {
+                boolean required = isPropertyRequired(rows, key);
+                String defaultValue = getPropertyDefaultValue(rows, key);
+
+                if (!required && defaultValue != null) {
+                    if (defaultValue.equals(value)) {
+                        continue;
+                    }
+                }
+            }
+
+            // we should keep this in the answer
+            answer.put(key, value);
+        }
+
+        // now parse the uri parameters
+        Map<String, Object> parameters = URISupport.parseParameters(u);
+
+        // and covert the values to String so its JMX friendly
+        while (!parameters.isEmpty()) {
+            Map.Entry<String, Object> entry = parameters.entrySet().iterator().next();
+            String key = entry.getKey();
+            String value = entry.getValue() != null ? entry.getValue().toString() : "";
+
+            boolean multiValued = isPropertyMultiValue(rows, key);
+            if (multiValued) {
+                String prefix = getPropertyPrefix(rows, key);
+                // extra all the multi valued options
+                Map<String, Object> values = URISupport.extractProperties(parameters, prefix);
+                // build a string with the extra multi valued options with the prefix and & as separator
+                CollectionStringBuffer csb = new CollectionStringBuffer("&");
+                for (Map.Entry<String, Object> multi : values.entrySet()) {
+                    String line = prefix + multi.getKey() + "=" + (multi.getValue() != null ? multi.getValue().toString() : "");
+                    csb.append(line);
+                }
+                // append the extra multi-values to the existing (which contains the first multi value)
+                if (!csb.isEmpty()) {
+                    value = value + "&" + csb.toString();
+                }
+            }
+
+            answer.put(key, value);
+            // remove the parameter as we run in a while loop until no more parameters
+            parameters.remove(key);
+        }
+
+        return answer;
+    }
+
+    public Map<String, String> endpointLenientProperties(String uri) throws URISyntaxException {
+        // need to normalize uri first
+
+        // parse the uri
+        URI u = normalizeUri(uri);
+        String scheme = u.getScheme();
+
+        String json = jsonSchemaResolver.getComponentJSonSchema(scheme);
+        if (json == null) {
+            throw new IllegalArgumentException("Cannot find endpoint with scheme " + scheme);
+        }
+
+        List<Map<String, String>> rows = JSonSchemaHelper.parseJsonSchema("properties", json, true);
+
+        // now parse the uri parameters
+        Map<String, Object> parameters = URISupport.parseParameters(u);
+
+        // all the known options
+        Set<String> names = getNames(rows);
+
+        Map<String, String> answer = new LinkedHashMap<>();
+
+        // and covert the values to String so its JMX friendly
+        parameters.forEach((k, v) -> {
+            String key = k;
+            String value = v != null ? v.toString() : "";
+
+            // is the key a prefix property
+            int dot = key.indexOf('.');
+            if (dot != -1) {
+                String prefix = key.substring(0, dot + 1); // include dot in prefix
+                String option = getPropertyNameFromNameWithPrefix(rows, prefix);
+                if (option == null || !isPropertyMultiValue(rows, option)) {
+                    answer.put(key, value);
+                }
+            } else if (!names.contains(key)) {
+                answer.put(key, value);
+            }
+        });
+
+        return answer;
+    }
+
+    public String endpointComponentName(String uri) {
+        if (uri != null) {
+            int idx = uri.indexOf(":");
+            if (idx > 0) {
+                return uri.substring(0, idx);
+            }
+        }
+        return null;
+    }
+
+    public String asEndpointUri(String scheme, String json, boolean encode) throws URISyntaxException {
+        return doAsEndpointUri(scheme, json, "&", encode);
+    }
+
+    public String asEndpointUriXml(String scheme, String json, boolean encode) throws URISyntaxException {
+        return doAsEndpointUri(scheme, json, "&amp;", encode);
+    }
+
+    private String doAsEndpointUri(String scheme, String json, String ampersand, boolean encode) throws URISyntaxException {
+        List<Map<String, String>> rows = JSonSchemaHelper.parseJsonSchema("properties", json, true);
+
+        Map<String, String> copy = new HashMap<String, String>();
+        for (Map<String, String> row : rows) {
+            String name = row.get("name");
+            String required = row.get("required");
+            String value = row.get("value");
+            String defaultValue = row.get("defaultValue");
+
+            // only add if either required, or the value is != default value
+            String valueToAdd = null;
+            if ("true".equals(required)) {
+                valueToAdd = value != null ? value : defaultValue;
+                if (valueToAdd == null) {
+                    valueToAdd = "";
+                }
+            } else {
+                // if we have a value and no default then add it
+                if (value != null && defaultValue == null) {
+                    valueToAdd = value;
+                }
+                // otherwise only add if the value is != default value
+                if (value != null && defaultValue != null && !value.equals(defaultValue)) {
+                    valueToAdd = value;
+                }
+            }
+
+            if (valueToAdd != null) {
+                copy.put(name, valueToAdd);
+            }
+        }
+
+        return doAsEndpointUri(scheme, copy, ampersand, encode);
+    }
+
+    public String asEndpointUri(String scheme, Map<String, String> properties, boolean encode) throws URISyntaxException {
+        return doAsEndpointUri(scheme, properties, "&", encode);
+    }
+
+    public String asEndpointUriXml(String scheme, Map<String, String> properties, boolean encode) throws URISyntaxException {
+        return doAsEndpointUri(scheme, properties, "&amp;", encode);
+    }
+
+    private String doAsEndpointUri(String scheme, Map<String, String> properties, String ampersand, boolean encode) throws URISyntaxException {
+        String json = jsonSchemaResolver.getComponentJSonSchema(scheme);
+        if (json == null) {
+            throw new IllegalArgumentException("Cannot find endpoint with scheme " + scheme);
+        }
+
+        // grab the syntax
+        String syntax = null;
+        List<Map<String, String>> rows = JSonSchemaHelper.parseJsonSchema("component", json, false);
+        for (Map<String, String> row : rows) {
+            if (row.containsKey("syntax")) {
+                syntax = row.get("syntax");
+                break;
+            }
+        }
+        if (syntax == null) {
+            throw new IllegalArgumentException("Endpoint with scheme " + scheme + " has no syntax defined in the json schema");
+        }
+
+        // do any properties filtering which can be needed for some special components
+        properties = filterProperties(scheme, properties);
+
+        rows = JSonSchemaHelper.parseJsonSchema("properties", json, true);
+
+        // clip the scheme from the syntax
+        syntax = after(syntax, ":");
+
+        String originalSyntax = syntax;
+
+        // build at first according to syntax (use a tree map as we want the uri options sorted)
+        Map<String, String> copy = new TreeMap<String, String>();
+        for (Map.Entry<String, String> entry : properties.entrySet()) {
+            String key = entry.getKey();
+            String value = entry.getValue() != null ? entry.getValue() : "";
+            if (syntax != null && syntax.contains(key)) {
+                syntax = syntax.replace(key, value);
+            } else {
+                copy.put(key, value);
+            }
+        }
+
+        // the tokens between the options in the path
+        String[] tokens = syntax.split("\\w+");
+
+        // parse the syntax into each options
+        Matcher matcher = SYNTAX_PATTERN.matcher(originalSyntax);
+        List<String> options = new ArrayList<String>();
+        while (matcher.find()) {
+            String s = matcher.group(1);
+            options.add(s);
+        }
+
+        // need to preserve {{ and }} from the syntax
+        // (we need to use words only as its provisional placeholders)
+        syntax = syntax.replaceAll("\\{\\{", "BEGINCAMELPLACEHOLDER");
+        syntax = syntax.replaceAll("\\}\\}", "ENDCAMELPLACEHOLDER");
+
+        // parse the syntax into each options
+        Matcher matcher2 = SYNTAX_PATTERN.matcher(syntax);
+        List<String> options2 = new ArrayList<String>();
+        while (matcher2.find()) {
+            String s = matcher2.group(1);
+            s = s.replaceAll("BEGINCAMELPLACEHOLDER", "\\{\\{");
+            s = s.replaceAll("ENDCAMELPLACEHOLDER", "\\}\\}");
+            options2.add(s);
+        }
+
+        // build the endpoint
+        StringBuilder sb = new StringBuilder();
+        sb.append(scheme);
+        sb.append(":");
+
+        int range = 0;
+        boolean first = true;
+        boolean hasQuestionmark = false;
+        for (int i = 0; i < options.size(); i++) {
+            String key = options.get(i);
+            String key2 = options2.get(i);
+            String token = null;
+            if (tokens.length > i) {
+                token = tokens[i];
+            }
+
+            boolean contains = properties.containsKey(key);
+            if (!contains) {
+                // if the key are similar we have no explicit value and can try to find a default value if the option is required
+                if (isPropertyRequired(rows, key)) {
+                    String value = getPropertyDefaultValue(rows, key);
+                    if (value != null) {
+                        properties.put(key, value);
+                        key2 = value;
+                    }
+                }
+            }
+
+            // was the option provided?
+            if (properties.containsKey(key)) {
+                if (!first && token != null) {
+                    sb.append(token);
+                }
+                hasQuestionmark |= key.contains("?") || (token != null && token.contains("?"));
+                sb.append(key2);
+                first = false;
+            }
+            range++;
+        }
+        // append any extra options that was in surplus for the last
+        while (range < options2.size()) {
+            String token = null;
+            if (tokens.length > range) {
+                token = tokens[range];
+            }
+            String key2 = options2.get(range);
+            sb.append(token);
+            sb.append(key2);
+            hasQuestionmark |= key2.contains("?") || (token != null && token.contains("?"));
+            range++;
+        }
+
+        if (!copy.isEmpty()) {
+            // the last option may already contain a ? char, if so we should use & instead of ?
+            sb.append(hasQuestionmark ? ampersand : '?');
+            String query = createQueryString(copy, ampersand, encode);
+            sb.append(query);
+        }
+
+        return sb.toString();
+    }
+
+    public SimpleValidationResult validateSimpleExpression(String simple) {
+        return doValidateSimple(null, simple, false);
+    }
+
+    public SimpleValidationResult validateSimpleExpression(ClassLoader classLoader, String simple) {
+        return doValidateSimple(classLoader, simple, false);
+    }
+
+    public SimpleValidationResult validateSimplePredicate(String simple) {
+        return doValidateSimple(null, simple, true);
+    }
+
+    public SimpleValidationResult validateSimplePredicate(ClassLoader classLoader, String simple) {
+        return doValidateSimple(classLoader, simple, true);
+    }
+
+    private SimpleValidationResult doValidateSimple(ClassLoader classLoader, String simple, boolean predicate) {
+        if (classLoader == null) {
+            classLoader = getClass().getClassLoader();
+        }
+
+        // if there are {{ }}} property placeholders then we need to resolve them to something else
+        // as the simple parse cannot resolve them before parsing as we dont run the actual Camel application
+        // with property placeholders setup so we need to dummy this by replace the {{ }} to something else
+        // therefore we use an more unlikely character: {{XXX}} to ~^XXX^~
+        String resolved = simple.replaceAll("\\{\\{(.+)\\}\\}", "~^$1^~");
+
+        SimpleValidationResult answer = new SimpleValidationResult(simple);
+
+        Object instance = null;
+        Class clazz = null;
+        try {
+            clazz = classLoader.loadClass("org.apache.camel.language.simple.SimpleLanguage");
+            instance = clazz.newInstance();
+        } catch (Exception e) {
+            // ignore
+        }
+
+        if (clazz != null && instance != null) {
+            Throwable cause = null;
+            try {
+                if (predicate) {
+                    instance.getClass().getMethod("createPredicate", String.class).invoke(instance, resolved);
+                } else {
+                    instance.getClass().getMethod("createExpression", String.class).invoke(instance, resolved);
+                }
+            } catch (InvocationTargetException e) {
+                cause = e.getTargetException();
+            } catch (Exception e) {
+                cause = e;
+            }
+
+            if (cause != null) {
+
+                // reverse ~^XXX^~ back to {{XXX}}
+                String errMsg = cause.getMessage();
+                errMsg = errMsg.replaceAll("\\~\\^(.+)\\^\\~", "{{$1}}");
+
+                answer.setError(errMsg);
+
+                // is it simple parser exception then we can grab the index where the problem is
+                if (cause.getClass().getName().equals("org.apache.camel.language.simple.types.SimpleIllegalSyntaxException")
+                    || cause.getClass().getName().equals("org.apache.camel.language.simple.types.SimpleParserException")) {
+                    try {
+                        // we need to grab the index field from those simple parser exceptions
+                        Method method = cause.getClass().getMethod("getIndex");
+                        Object result = method.invoke(cause);
+                        if (result != null) {
+                            int index = (int) result;
+                            answer.setIndex(index);
+                        }
+                    } catch (Throwable i) {
+                        // ignore
+                    }
+                }
+
+                // we need to grab the short message field from this simple syntax exception
+                if (cause.getClass().getName().equals("org.apache.camel.language.simple.types.SimpleIllegalSyntaxException")) {
+                    try {
+                        Method method = cause.getClass().getMethod("getShortMessage");
+                        Object result = method.invoke(cause);
+                        if (result != null) {
+                            String msg = (String) result;
+                            answer.setShortError(msg);
+                        }
+                    } catch (Throwable i) {
+                        // ignore
+                    }
+
+                    if (answer.getShortError() == null) {
+                        // fallback and try to make existing message short instead
+                        String msg = answer.getError();
+                        // grab everything before " at location " which would be regarded as the short message
+                        int idx = msg.indexOf(" at location ");
+                        if (idx > 0) {
+                            msg = msg.substring(0, idx);
+                            answer.setShortError(msg);
+                        }
+                    }
+                }
+            }
+        }
+
+        return answer;
+    }
+
+    public LanguageValidationResult validateLanguagePredicate(ClassLoader classLoader, String language, String text) {
+        return doValidateLanguage(classLoader, language, text, true);
+    }
+
+    public LanguageValidationResult validateLanguageExpression(ClassLoader classLoader, String language, String text) {
+        return doValidateLanguage(classLoader, language, text, false);
+    }
+
+    private LanguageValidationResult doValidateLanguage(ClassLoader classLoader, String language, String text, boolean predicate) {
+        if (classLoader == null) {
+            classLoader = getClass().getClassLoader();
+        }
+
+        SimpleValidationResult answer = new SimpleValidationResult(text);
+
+        String json = jsonSchemaResolver.getLanguageJSonSchema(language);
+        if (json == null) {
+            answer.setError("Unknown language " + language);
+            return answer;
+        }
+
+        List<Map<String, String>> rows = JSonSchemaHelper.parseJsonSchema("language", json, false);
+        String className = null;
+        for (Map<String, String> row : rows) {
+            if (row.containsKey("javaType")) {
+                className = row.get("javaType");
+            }
+        }
+
+        if (className == null) {
+            answer.setError("Cannot find javaType for language " + language);
+            return answer;
+        }
+
+        Object instance = null;
+        Class clazz = null;
+        try {
+            clazz = classLoader.loadClass(className);
+            instance = clazz.newInstance();
+        } catch (Exception e) {
+            // ignore
+        }
+
+        if (clazz != null && instance != null) {
+            Throwable cause = null;
+            try {
+                if (predicate) {
+                    instance.getClass().getMethod("createPredicate", String.class).invoke(instance, text);
+                } else {
+                    instance.getClass().getMethod("createExpression", String.class).invoke(instance, text);
+                }
+            } catch (InvocationTargetException e) {
+                cause = e.getTargetException();
+            } catch (Exception e) {
+                cause = e;
+            }
+
+            if (cause != null) {
+                answer.setError(cause.getMessage());
+            }
+        }
+
+        return answer;
+    }
+
+    /**
+     * Special logic for log endpoints to deal when showAll=true
+     */
+    private Map<String, String> filterProperties(String scheme, Map<String, String> options) {
+        if ("log".equals(scheme)) {
+            String showAll = options.get("showAll");
+            if ("true".equals(showAll)) {
+                Map<String, String> filtered = new LinkedHashMap<String, String>();
+                // remove all the other showXXX options when showAll=true
+                for (Map.Entry<String, String> entry : options.entrySet()) {
+                    String key = entry.getKey();
+                    boolean skip = key.startsWith("show") && !key.equals("showAll");
+                    if (!skip) {
+                        filtered.put(key, entry.getValue());
+                    }
+                }
+                return filtered;
+            }
+        }
+        // use as-is
+        return options;
+    }
+
+    private static boolean validateInteger(String value) {
+        boolean valid = false;
+        try {
+            valid = Integer.valueOf(value) != null;
+        } catch (Exception e) {
+            // ignore
+        }
+        if (!valid) {
+            // it may be a time pattern, such as 5s for 5 seconds = 5000
+            try {
+                TimePatternConverter.toMilliSeconds(value);
+                valid = true;
+            } catch (Exception e) {
+                // ignore
+            }
+        }
+        return valid;
+    }
+
+    // CHECKSTYLE:ON
+
+}

http://git-wip-us.apache.org/repos/asf/camel/blob/f5848e39/platforms/camel-catalog/src/main/java/org/apache/camel/catalog/CamelCatalogJSonSchemaResolver.java
----------------------------------------------------------------------
diff --git a/platforms/camel-catalog/src/main/java/org/apache/camel/catalog/CamelCatalogJSonSchemaResolver.java b/platforms/camel-catalog/src/main/java/org/apache/camel/catalog/CamelCatalogJSonSchemaResolver.java
new file mode 100644
index 0000000..2823fe3
--- /dev/null
+++ b/platforms/camel-catalog/src/main/java/org/apache/camel/catalog/CamelCatalogJSonSchemaResolver.java
@@ -0,0 +1,181 @@
+/**
+ * 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.catalog;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * {@link JSonSchemaResolver} used by {@link CamelCatalog} that is able to load all the resources that the complete camel-catalog JAR provides.
+ */
+public class CamelCatalogJSonSchemaResolver implements JSonSchemaResolver {
+
+    private static final String MODEL_DIR = "org/apache/camel/catalog/models";
+
+    private final CamelCatalog camelCatalog;
+
+    // 3rd party components/data-formats
+    private final Map<String, String> extraComponents;
+    private final Map<String, String> extraComponentsJSonSchema;
+    private final Map<String, String> extraDataFormats;
+    private final Map<String, String> extraDataFormatsJSonSchema;
+
+    public CamelCatalogJSonSchemaResolver(CamelCatalog camelCatalog,
+                                          Map<String, String> extraComponents, Map<String, String> extraComponentsJSonSchema,
+                                          Map<String, String> extraDataFormats, Map<String, String> extraDataFormatsJSonSchema) {
+        this.camelCatalog = camelCatalog;
+        this.extraComponents = extraComponents;
+        this.extraComponentsJSonSchema = extraComponentsJSonSchema;
+        this.extraDataFormats = extraDataFormats;
+        this.extraDataFormatsJSonSchema = extraDataFormatsJSonSchema;
+    }
+
+    @Override
+    public String getComponentJSonSchema(String name) {
+        String file = camelCatalog.getRuntimeProvider().getComponentJSonSchemaDirectory() + "/" + name + ".json";
+
+        String answer = null;
+        InputStream is = camelCatalog.getVersionManager().getResourceAsStream(file);
+        if (is != null) {
+            try {
+                answer = CatalogHelper.loadText(is);
+            } catch (IOException e) {
+                // ignore
+            }
+        }
+        if (answer == null) {
+            // its maybe a third party so try to see if we have the json schema already
+            answer = extraComponentsJSonSchema.get(name);
+            if (answer == null) {
+                // or if we can load it from the classpath
+                String className = extraComponents.get(name);
+                if (className != null) {
+                    String packageName = className.substring(0, className.lastIndexOf('.'));
+                    packageName = packageName.replace('.', '/');
+                    String path = packageName + "/" + name + ".json";
+                    is = camelCatalog.getVersionManager().getResourceAsStream(path);
+                    if (is != null) {
+                        try {
+                            answer = CatalogHelper.loadText(is);
+                        } catch (IOException e) {
+                            // ignore
+                        }
+                    }
+                }
+            }
+        }
+
+        return answer;
+    }
+
+    @Override
+    public String getDataFormatJSonSchema(String name) {
+        String file = camelCatalog.getRuntimeProvider().getDataFormatJSonSchemaDirectory() + "/" + name + ".json";
+
+        String answer = null;
+        InputStream is = camelCatalog.getVersionManager().getResourceAsStream(file);
+        if (is != null) {
+            try {
+                answer = CatalogHelper.loadText(is);
+            } catch (IOException e) {
+                // ignore
+            }
+        }
+        if (answer == null) {
+            // its maybe a third party so try to see if we have the json schema already
+            answer = extraDataFormatsJSonSchema.get(name);
+            if (answer == null) {
+                // or if we can load it from the classpath
+                String className = extraDataFormats.get(name);
+                if (className != null) {
+                    String packageName = className.substring(0, className.lastIndexOf('.'));
+                    packageName = packageName.replace('.', '/');
+                    String path = packageName + "/" + name + ".json";
+                    is = camelCatalog.getVersionManager().getResourceAsStream(path);
+                    if (is != null) {
+                        try {
+                            answer = CatalogHelper.loadText(is);
+                        } catch (IOException e) {
+                            // ignore
+                        }
+                    }
+                }
+            }
+        }
+
+        return answer;
+    }
+
+    @Override
+    public String getLanguageJSonSchema(String name) {
+        // if we try to look method then its in the bean.json file
+        if ("method".equals(name)) {
+            name = "bean";
+        }
+
+        String file = camelCatalog.getRuntimeProvider().getLanguageJSonSchemaDirectory() + "/" + name + ".json";
+
+        String answer = null;
+        InputStream is = camelCatalog.getVersionManager().getResourceAsStream(file);
+        if (is != null) {
+            try {
+                answer = CatalogHelper.loadText(is);
+            } catch (IOException e) {
+                // ignore
+            }
+        }
+
+        return answer;
+    }
+
+    @Override
+    public String getOtherJSonSchema(String name) {
+        String file = camelCatalog.getRuntimeProvider().getOtherJSonSchemaDirectory() + "/" + name + ".json";
+
+        String answer = null;
+        InputStream is = camelCatalog.getVersionManager().getResourceAsStream(file);
+        if (is != null) {
+            try {
+                answer = CatalogHelper.loadText(is);
+            } catch (IOException e) {
+                // ignore
+            }
+        }
+
+        return answer;
+    }
+
+    @Override
+    public String getModelJSonSchema(String name) {
+        String file = MODEL_DIR + "/" + name + ".json";
+
+        String answer = null;
+
+        InputStream is = camelCatalog.getVersionManager().getResourceAsStream(file);
+        if (is != null) {
+            try {
+                answer = CatalogHelper.loadText(is);
+            } catch (IOException e) {
+                // ignore
+            }
+        }
+
+        return answer;
+    }
+}