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&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, "&", 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, "&", 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;
+ }
+}