http://git-wip-us.apache.org/repos/asf/camel/blob/b23fbb46/platforms/camel-catalog/src/main/java/org/apache/camel/catalog/DefaultRuntimeProvider.java ---------------------------------------------------------------------- diff --git a/platforms/camel-catalog/src/main/java/org/apache/camel/catalog/DefaultRuntimeProvider.java b/platforms/camel-catalog/src/main/java/org/apache/camel/catalog/DefaultRuntimeProvider.java new file mode 100644 index 0000000..5e3ec9b --- /dev/null +++ b/platforms/camel-catalog/src/main/java/org/apache/camel/catalog/DefaultRuntimeProvider.java @@ -0,0 +1,160 @@ +/** + * 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.ArrayList; +import java.util.List; + +public class DefaultRuntimeProvider implements RuntimeProvider { + + private static final String COMPONENT_DIR = "org/apache/camel/catalog/components"; + private static final String DATAFORMAT_DIR = "org/apache/camel/catalog/dataformats"; + private static final String LANGUAGE_DIR = "org/apache/camel/catalog/languages"; + private static final String OTHER_DIR = "org/apache/camel/catalog/others"; + private static final String COMPONENTS_CATALOG = "org/apache/camel/catalog/components.properties"; + private static final String DATA_FORMATS_CATALOG = "org/apache/camel/catalog/dataformats.properties"; + private static final String LANGUAGE_CATALOG = "org/apache/camel/catalog/languages.properties"; + private static final String OTHER_CATALOG = "org/apache/camel/catalog/others.properties"; + + private CamelCatalog camelCatalog; + + public DefaultRuntimeProvider() { + } + + public DefaultRuntimeProvider(CamelCatalog camelCatalog) { + this.camelCatalog = camelCatalog; + } + + @Override + public CamelCatalog getCamelCatalog() { + return camelCatalog; + } + + @Override + public void setCamelCatalog(CamelCatalog camelCatalog) { + this.camelCatalog = camelCatalog; + } + + @Override + public String getProviderName() { + return "default"; + } + + @Override + public String getProviderGroupId() { + return "org.apache.camel"; + } + + @Override + public String getProviderArtifactId() { + return "camel-catalog"; + } + + @Override + public String getComponentJSonSchemaDirectory() { + return COMPONENT_DIR; + } + + @Override + public String getDataFormatJSonSchemaDirectory() { + return DATAFORMAT_DIR; + } + + @Override + public String getLanguageJSonSchemaDirectory() { + return LANGUAGE_DIR; + } + + @Override + public String getOtherJSonSchemaDirectory() { + return OTHER_DIR; + } + + protected String getComponentsCatalog() { + return COMPONENTS_CATALOG; + } + + protected String getDataFormatsCatalog() { + return DATA_FORMATS_CATALOG; + } + + protected String getLanguageCatalog() { + return LANGUAGE_CATALOG; + } + + protected String getOtherCatalog() { + return OTHER_CATALOG; + } + + @Override + public List<String> findComponentNames() { + List<String> names = new ArrayList<String>(); + InputStream is = getCamelCatalog().getVersionManager().getResourceAsStream(getComponentsCatalog()); + if (is != null) { + try { + CatalogHelper.loadLines(is, names); + } catch (IOException e) { + // ignore + } + } + return names; + } + + @Override + public List<String> findDataFormatNames() { + List<String> names = new ArrayList<String>(); + InputStream is = getCamelCatalog().getVersionManager().getResourceAsStream(getDataFormatsCatalog()); + if (is != null) { + try { + CatalogHelper.loadLines(is, names); + } catch (IOException e) { + // ignore + } + } + return names; + } + + @Override + public List<String> findLanguageNames() { + List<String> names = new ArrayList<String>(); + InputStream is = getCamelCatalog().getVersionManager().getResourceAsStream(getLanguageCatalog()); + if (is != null) { + try { + CatalogHelper.loadLines(is, names); + } catch (IOException e) { + // ignore + } + } + return names; + } + + @Override + public List<String> findOtherNames() { + List<String> names = new ArrayList<String>(); + InputStream is = getCamelCatalog().getVersionManager().getResourceAsStream(getOtherCatalog()); + if (is != null) { + try { + CatalogHelper.loadLines(is, names); + } catch (IOException e) { + // ignore + } + } + return names; + } +}
http://git-wip-us.apache.org/repos/asf/camel/blob/b23fbb46/platforms/camel-catalog/src/main/java/org/apache/camel/catalog/DefaultVersionManager.java ---------------------------------------------------------------------- diff --git a/platforms/camel-catalog/src/main/java/org/apache/camel/catalog/DefaultVersionManager.java b/platforms/camel-catalog/src/main/java/org/apache/camel/catalog/DefaultVersionManager.java new file mode 100644 index 0000000..c30ddbc --- /dev/null +++ b/platforms/camel-catalog/src/main/java/org/apache/camel/catalog/DefaultVersionManager.java @@ -0,0 +1,55 @@ +/** + * 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.InputStream; + +public class DefaultVersionManager implements VersionManager { + + private final CamelCatalog camelCatalog; + + public DefaultVersionManager(CamelCatalog camelCatalog) { + this.camelCatalog = camelCatalog; + } + + @Override + public String getLoadedVersion() { + return camelCatalog.getCatalogVersion(); + } + + @Override + public boolean loadVersion(String version) { + return getLoadedVersion().equals(version); + } + + @Override + public String getRuntimeProviderLoadedVersion() { + // not supported + return null; + } + + @Override + public boolean loadRuntimeProviderVersion(String groupId, String artifactId, String version) { + // not supported + return false; + } + + @Override + public InputStream getResourceAsStream(String name) { + return DefaultCamelCatalog.class.getClassLoader().getResourceAsStream(name); + } +} http://git-wip-us.apache.org/repos/asf/camel/blob/b23fbb46/platforms/camel-catalog/src/main/java/org/apache/camel/catalog/EndpointValidationResult.java ---------------------------------------------------------------------- diff --git a/platforms/camel-catalog/src/main/java/org/apache/camel/catalog/EndpointValidationResult.java b/platforms/camel-catalog/src/main/java/org/apache/camel/catalog/EndpointValidationResult.java new file mode 100644 index 0000000..11e2c5e --- /dev/null +++ b/platforms/camel-catalog/src/main/java/org/apache/camel/catalog/EndpointValidationResult.java @@ -0,0 +1,426 @@ +/** + * 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.Serializable; +import java.util.Arrays; +import java.util.LinkedHashMap; +import java.util.LinkedHashSet; +import java.util.Map; +import java.util.Set; + +import static org.apache.camel.catalog.URISupport.isEmpty; + +/** + * Details result of validating endpoint uri. + */ +public class EndpointValidationResult implements Serializable { + + private final String uri; + private int errors; + + // general + private String syntaxError; + private String unknownComponent; + private String incapable; + + // options + private Set<String> unknown; + private Map<String, String[]> unknownSuggestions; + private Set<String> lenient; + private Set<String> notConsumerOnly; + private Set<String> notProducerOnly; + private Set<String> required; + private Map<String, String> invalidEnum; + private Map<String, String[]> invalidEnumChoices; + private Map<String, String[]> invalidEnumSuggestions; + private Map<String, String> invalidReference; + private Map<String, String> invalidBoolean; + private Map<String, String> invalidInteger; + private Map<String, String> invalidNumber; + private Map<String, String> defaultValues; + + public EndpointValidationResult(String uri) { + this.uri = uri; + } + + public String getUri() { + return uri; + } + + public int getNumberOfErrors() { + return errors; + } + + public boolean isSuccess() { + boolean ok = syntaxError == null && unknownComponent == null && incapable == null + && unknown == null && required == null; + if (ok) { + ok = notConsumerOnly == null && notProducerOnly == null; + } + if (ok) { + ok = invalidEnum == null && invalidEnumChoices == null && invalidReference == null + && invalidBoolean == null && invalidInteger == null && invalidNumber == null; + } + return ok; + } + + public void addSyntaxError(String syntaxError) { + this.syntaxError = syntaxError; + errors++; + } + + public void addIncapable(String uri) { + this.incapable = uri; + errors++; + } + + public void addUnknownComponent(String name) { + this.unknownComponent = name; + errors++; + } + + public void addUnknown(String name) { + if (unknown == null) { + unknown = new LinkedHashSet<String>(); + } + if (!unknown.contains(name)) { + unknown.add(name); + errors++; + } + } + + public void addUnknownSuggestions(String name, String[] suggestions) { + if (unknownSuggestions == null) { + unknownSuggestions = new LinkedHashMap<String, String[]>(); + } + unknownSuggestions.put(name, suggestions); + } + + public void addLenient(String name) { + if (lenient == null) { + lenient = new LinkedHashSet<String>(); + } + if (!lenient.contains(name)) { + lenient.add(name); + } + } + + public void addRequired(String name) { + if (required == null) { + required = new LinkedHashSet<String>(); + } + if (!required.contains(name)) { + required.add(name); + errors++; + } + } + + public void addInvalidEnum(String name, String value) { + if (invalidEnum == null) { + invalidEnum = new LinkedHashMap<String, String>(); + } + if (!invalidEnum.containsKey(name)) { + invalidEnum.put(name, value); + errors++; + } + } + + public void addInvalidEnumChoices(String name, String[] choices) { + if (invalidEnumChoices == null) { + invalidEnumChoices = new LinkedHashMap<String, String[]>(); + } + invalidEnumChoices.put(name, choices); + } + + public void addInvalidEnumSuggestions(String name, String[] suggestions) { + if (invalidEnumSuggestions == null) { + invalidEnumSuggestions = new LinkedHashMap<String, String[]>(); + } + invalidEnumSuggestions.put(name, suggestions); + } + + public void addInvalidReference(String name, String value) { + if (invalidReference == null) { + invalidReference = new LinkedHashMap<String, String>(); + } + if (!invalidReference.containsKey(name)) { + invalidReference.put(name, value); + errors++; + } + } + + public void addInvalidBoolean(String name, String value) { + if (invalidBoolean == null) { + invalidBoolean = new LinkedHashMap<String, String>(); + } + if (!invalidBoolean.containsKey(name)) { + invalidBoolean.put(name, value); + errors++; + } + } + + public void addInvalidInteger(String name, String value) { + if (invalidInteger == null) { + invalidInteger = new LinkedHashMap<String, String>(); + } + if (!invalidInteger.containsKey(name)) { + invalidInteger.put(name, value); + errors++; + } + } + + public void addInvalidNumber(String name, String value) { + if (invalidNumber == null) { + invalidNumber = new LinkedHashMap<String, String>(); + } + if (!invalidNumber.containsKey(name)) { + invalidNumber.put(name, value); + errors++; + } + } + + public void addDefaultValue(String name, String value) { + if (defaultValues == null) { + defaultValues = new LinkedHashMap<String, String>(); + } + defaultValues.put(name, value); + } + + public void addNotConsumerOnly(String name) { + if (notConsumerOnly == null) { + notConsumerOnly = new LinkedHashSet<String>(); + } + if (!notConsumerOnly.contains(name)) { + notConsumerOnly.add(name); + errors++; + } + } + + public void addNotProducerOnly(String name) { + if (notProducerOnly == null) { + notProducerOnly = new LinkedHashSet<String>(); + } + if (!notProducerOnly.contains(name)) { + notProducerOnly.add(name); + errors++; + } + } + + public String getSyntaxError() { + return syntaxError; + } + + public String getIncapable() { + return incapable; + } + + public Set<String> getUnknown() { + return unknown; + } + + public Set<String> getLenient() { + return lenient; + } + + public Map<String, String[]> getUnknownSuggestions() { + return unknownSuggestions; + } + + public String getUnknownComponent() { + return unknownComponent; + } + + public Set<String> getRequired() { + return required; + } + + public Map<String, String> getInvalidEnum() { + return invalidEnum; + } + + public Map<String, String[]> getInvalidEnumChoices() { + return invalidEnumChoices; + } + + public Map<String, String> getInvalidReference() { + return invalidReference; + } + + public Map<String, String> getInvalidBoolean() { + return invalidBoolean; + } + + public Map<String, String> getInvalidInteger() { + return invalidInteger; + } + + public Map<String, String> getInvalidNumber() { + return invalidNumber; + } + + public Map<String, String> getDefaultValues() { + return defaultValues; + } + + public Set<String> getNotConsumerOnly() { + return notConsumerOnly; + } + + public Set<String> getNotProducerOnly() { + return notProducerOnly; + } + + /** + * A human readable summary of the validation errors. + * + * @param includeHeader whether to include a header + * @return the summary, or <tt>null</tt> if no validation errors + */ + public String summaryErrorMessage(boolean includeHeader) { + if (isSuccess()) { + return null; + } + + if (incapable != null) { + return "\tIncapable of parsing uri: " + incapable; + } else if (syntaxError != null) { + return "\tSyntax error: " + syntaxError; + } else if (unknownComponent != null) { + return "\tUnknown component: " + unknownComponent; + } + + // for each invalid option build a reason message + Map<String, String> options = new LinkedHashMap<String, String>(); + if (unknown != null) { + for (String name : unknown) { + if (unknownSuggestions != null && unknownSuggestions.containsKey(name)) { + String[] suggestions = unknownSuggestions.get(name); + if (suggestions != null && suggestions.length > 0) { + String str = Arrays.asList(suggestions).toString(); + options.put(name, "Unknown option. Did you mean: " + str); + } else { + options.put(name, "Unknown option"); + } + } else { + options.put(name, "Unknown option"); + } + } + } + if (notConsumerOnly != null) { + for (String name : notConsumerOnly) { + options.put(name, "Option not applicable in consumer only mode"); + } + } + if (notProducerOnly != null) { + for (String name : notProducerOnly) { + options.put(name, "Option not applicable in producer only mode"); + } + } + if (required != null) { + for (String name : required) { + options.put(name, "Missing required option"); + } + } + if (invalidEnum != null) { + for (Map.Entry<String, String> entry : invalidEnum.entrySet()) { + String name = entry.getKey(); + String[] choices = invalidEnumChoices.get(name); + String defaultValue = defaultValues != null ? defaultValues.get(entry.getKey()) : null; + String str = Arrays.asList(choices).toString(); + String msg = "Invalid enum value: " + entry.getValue() + ". Possible values: " + str; + if (invalidEnumSuggestions != null) { + String[] suggestions = invalidEnumSuggestions.get(name); + if (suggestions != null && suggestions.length > 0) { + str = Arrays.asList(suggestions).toString(); + msg += ". Did you mean: " + str; + } + } + if (defaultValue != null) { + msg += ". Default value: " + defaultValue; + } + + options.put(entry.getKey(), msg); + } + } + if (invalidReference != null) { + for (Map.Entry<String, String> entry : invalidReference.entrySet()) { + boolean empty = isEmpty(entry.getValue()); + if (empty) { + options.put(entry.getKey(), "Empty reference value"); + } else if (!entry.getValue().startsWith("#")) { + options.put(entry.getKey(), "Invalid reference value: " + entry.getValue() + " must start with #"); + } else { + options.put(entry.getKey(), "Invalid reference value: " + entry.getValue()); + } + } + } + if (invalidBoolean != null) { + for (Map.Entry<String, String> entry : invalidBoolean.entrySet()) { + boolean empty = isEmpty(entry.getValue()); + if (empty) { + options.put(entry.getKey(), "Empty boolean value"); + } else { + options.put(entry.getKey(), "Invalid boolean value: " + entry.getValue()); + } + } + } + if (invalidInteger != null) { + for (Map.Entry<String, String> entry : invalidInteger.entrySet()) { + boolean empty = isEmpty(entry.getValue()); + if (empty) { + options.put(entry.getKey(), "Empty integer value"); + } else { + options.put(entry.getKey(), "Invalid integer value: " + entry.getValue()); + } + } + } + if (invalidNumber != null) { + for (Map.Entry<String, String> entry : invalidNumber.entrySet()) { + boolean empty = isEmpty(entry.getValue()); + if (empty) { + options.put(entry.getKey(), "Empty number value"); + } else { + options.put(entry.getKey(), "Invalid number value: " + entry.getValue()); + } + } + } + + // build a table with the error summary nicely formatted + // lets use 24 as min length + int maxLen = 24; + for (String key : options.keySet()) { + maxLen = Math.max(maxLen, key.length()); + } + String format = "%" + maxLen + "s %s"; + + // build the human error summary + StringBuilder sb = new StringBuilder(); + if (includeHeader) { + sb.append("Endpoint validator error\n"); + sb.append("---------------------------------------------------------------------------------------------------------------------------------------\n"); + sb.append("\n"); + } + sb.append("\t").append(uri).append("\n"); + for (Map.Entry<String, String> option : options.entrySet()) { + String out = String.format(format, option.getKey(), option.getValue()); + sb.append("\n\t").append(out); + } + + return sb.toString(); + } +} http://git-wip-us.apache.org/repos/asf/camel/blob/b23fbb46/platforms/camel-catalog/src/main/java/org/apache/camel/catalog/JSonSchemaHelper.java ---------------------------------------------------------------------- diff --git a/platforms/camel-catalog/src/main/java/org/apache/camel/catalog/JSonSchemaHelper.java b/platforms/camel-catalog/src/main/java/org/apache/camel/catalog/JSonSchemaHelper.java new file mode 100644 index 0000000..318cbf4 --- /dev/null +++ b/platforms/camel-catalog/src/main/java/org/apache/camel/catalog/JSonSchemaHelper.java @@ -0,0 +1,424 @@ +/** + * 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.util.ArrayList; +import java.util.LinkedHashMap; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +public final class JSonSchemaHelper { + + // 0 = text, 1 = enum, 2 = boolean, 3 = integer or number + private static final Pattern PATTERN = Pattern.compile("\"(.+?)\"|\\[(.+)\\]|(true|false)|(\\d+\\.?\\d*)"); + private static final String QUOT = """; + + private JSonSchemaHelper() { + } + + /** + * Parses the json schema to split it into a list or rows, where each row contains key value pairs with the metadata + * + * @param group the group to parse from such as <tt>component</tt>, <tt>componentProperties</tt>, or <tt>properties</tt>. + * @param json the json + * @return a list of all the rows, where each row is a set of key value pairs with metadata + */ + public static List<Map<String, String>> parseJsonSchema(String group, String json, boolean parseProperties) { + List<Map<String, String>> answer = new ArrayList<Map<String, String>>(); + if (json == null) { + return answer; + } + + boolean found = false; + + // parse line by line + String[] lines = json.split("\n"); + for (String line : lines) { + // we need to find the group first + if (!found) { + String s = line.trim(); + found = s.startsWith("\"" + group + "\":") && s.endsWith("{"); + continue; + } + + // we should stop when we end the group + if (line.equals(" },") || line.equals(" }")) { + break; + } + + // need to safe encode \" so we can parse the line + line = line.replaceAll("\"\\\\\"\"", '"' + QUOT + '"'); + + Map<String, String> row = new LinkedHashMap<String, String>(); + Matcher matcher = PATTERN.matcher(line); + + String key; + if (parseProperties) { + // when parsing properties the first key is given as name, so the first parsed token is the value of the name + key = "name"; + } else { + key = null; + } + while (matcher.find()) { + if (key == null) { + key = matcher.group(1); + } else { + String value = matcher.group(1); + if (value != null) { + // its text based + value = value.trim(); + // decode + value = value.replaceAll(QUOT, "\""); + value = decodeJson(value); + } + if (value == null) { + // not text then its maybe an enum? + value = matcher.group(2); + if (value != null) { + // its an enum so strip out " and trim spaces after comma + value = value.replaceAll("\"", ""); + value = value.replaceAll(", ", ","); + value = value.trim(); + } + } + if (value == null) { + // not text then its maybe a boolean? + value = matcher.group(3); + } + if (value == null) { + // not text then its maybe a integer? + value = matcher.group(4); + } + if (value != null) { + row.put(key, value); + } + // reset + key = null; + } + } + if (!row.isEmpty()) { + answer.add(row); + } + } + + return answer; + } + + private static String decodeJson(String value) { + // json encodes a \ as \\ so we need to decode from \\ back to \ + if ("\\\\".equals(value)) { + value = "\\"; + } + return value; + } + + public static boolean isComponentLenientProperties(List<Map<String, String>> rows) { + for (Map<String, String> row : rows) { + if (row.containsKey("lenientProperties")) { + return "true".equals(row.get("lenientProperties")); + } + } + return false; + } + + public static boolean isComponentConsumerOnly(List<Map<String, String>> rows) { + for (Map<String, String> row : rows) { + if (row.containsKey("consumerOnly")) { + return "true".equals(row.get("consumerOnly")); + } + } + return false; + } + + public static boolean isComponentProducerOnly(List<Map<String, String>> rows) { + for (Map<String, String> row : rows) { + if (row.containsKey("producerOnly")) { + return "true".equals(row.get("producerOnly")); + } + } + return false; + } + + public static boolean isPropertyConsumerOnly(List<Map<String, String>> rows, String name) { + for (Map<String, String> row : rows) { + String labels = null; + boolean found = false; + if (row.containsKey("name")) { + found = name.equals(row.get("name")); + } + if (row.containsKey("label")) { + labels = row.get("label"); + } + if (found) { + return labels != null && labels.contains("consumer"); + } + } + return false; + } + + public static boolean isPropertyProducerOnly(List<Map<String, String>> rows, String name) { + for (Map<String, String> row : rows) { + String labels = null; + boolean found = false; + if (row.containsKey("name")) { + found = name.equals(row.get("name")); + } + if (row.containsKey("label")) { + labels = row.get("label"); + } + if (found) { + return labels != null && labels.contains("producer"); + } + } + return false; + } + + public static boolean isPropertyRequired(List<Map<String, String>> rows, String name) { + for (Map<String, String> row : rows) { + boolean required = false; + boolean found = false; + if (row.containsKey("name")) { + found = name.equals(row.get("name")); + } + if (row.containsKey("required")) { + required = "true".equals(row.get("required")); + } + if (found) { + return required; + } + } + return false; + } + + public static String getPropertyKind(List<Map<String, String>> rows, String name) { + for (Map<String, String> row : rows) { + String kind = null; + boolean found = false; + if (row.containsKey("name")) { + found = name.equals(row.get("name")); + } + if (row.containsKey("kind")) { + kind = row.get("kind"); + } + if (found) { + return kind; + } + } + return null; + } + + public static boolean isPropertyBoolean(List<Map<String, String>> rows, String name) { + for (Map<String, String> row : rows) { + String type = null; + boolean found = false; + if (row.containsKey("name")) { + found = name.equals(row.get("name")); + } + if (row.containsKey("type")) { + type = row.get("type"); + } + if (found) { + return "boolean".equals(type); + } + } + return false; + } + + public static boolean isPropertyInteger(List<Map<String, String>> rows, String name) { + for (Map<String, String> row : rows) { + String type = null; + boolean found = false; + if (row.containsKey("name")) { + found = name.equals(row.get("name")); + } + if (row.containsKey("type")) { + type = row.get("type"); + } + if (found) { + return "integer".equals(type); + } + } + return false; + } + + public static boolean isPropertyNumber(List<Map<String, String>> rows, String name) { + for (Map<String, String> row : rows) { + String type = null; + boolean found = false; + if (row.containsKey("name")) { + found = name.equals(row.get("name")); + } + if (row.containsKey("type")) { + type = row.get("type"); + } + if (found) { + return "number".equals(type); + } + } + return false; + } + + public static boolean isPropertyObject(List<Map<String, String>> rows, String name) { + for (Map<String, String> row : rows) { + String type = null; + boolean found = false; + if (row.containsKey("name")) { + found = name.equals(row.get("name")); + } + if (row.containsKey("type")) { + type = row.get("type"); + } + if (found) { + return "object".equals(type); + } + } + return false; + } + + public static String getPropertyDefaultValue(List<Map<String, String>> rows, String name) { + for (Map<String, String> row : rows) { + String defaultValue = null; + boolean found = false; + if (row.containsKey("name")) { + found = name.equals(row.get("name")); + } + if (row.containsKey("defaultValue")) { + defaultValue = row.get("defaultValue"); + } + if (found) { + return defaultValue; + } + } + return null; + } + + public static String stripOptionalPrefixFromName(List<Map<String, String>> rows, String name) { + for (Map<String, String> row : rows) { + String optionalPrefix = null; + boolean found = false; + if (row.containsKey("optionalPrefix")) { + optionalPrefix = row.get("optionalPrefix"); + } + if (row.containsKey("name")) { + if (optionalPrefix != null && name.startsWith(optionalPrefix)) { + name = name.substring(optionalPrefix.length()); + // try again + return stripOptionalPrefixFromName(rows, name); + } else { + found = name.equals(row.get("name")); + } + } + if (found) { + return name; + } + } + return name; + } + + public static String getPropertyEnum(List<Map<String, String>> rows, String name) { + for (Map<String, String> row : rows) { + String enums = null; + boolean found = false; + if (row.containsKey("name")) { + found = name.equals(row.get("name")); + } + if (row.containsKey("enum")) { + enums = row.get("enum"); + } + if (found) { + return enums; + } + } + return null; + } + + public static String getPropertyPrefix(List<Map<String, String>> rows, String name) { + for (Map<String, String> row : rows) { + String prefix = null; + boolean found = false; + if (row.containsKey("name")) { + found = name.equals(row.get("name")); + } + if (row.containsKey("prefix")) { + prefix = row.get("prefix"); + } + if (found) { + return prefix; + } + } + return null; + } + + public static boolean isPropertyMultiValue(List<Map<String, String>> rows, String name) { + for (Map<String, String> row : rows) { + boolean multiValue = false; + boolean found = false; + if (row.containsKey("name")) { + found = name.equals(row.get("name")); + } + if (row.containsKey("multiValue")) { + multiValue = "true".equals(row.get("multiValue")); + } + if (found) { + return multiValue; + } + } + return false; + } + + public static String getPropertyNameFromNameWithPrefix(List<Map<String, String>> rows, String name) { + for (Map<String, String> row : rows) { + String propertyName = null; + boolean found = false; + if (row.containsKey("name")) { + propertyName = row.get("name"); + } + if (row.containsKey("prefix")) { + String preifx = row.get("prefix"); + found = name.startsWith(preifx); + } + if (found) { + return propertyName; + } + } + return null; + } + + public static Map<String, String> getRow(List<Map<String, String>> rows, String key) { + for (Map<String, String> row : rows) { + if (key.equals(row.get("name"))) { + return row; + } + } + return null; + } + + public static Set<String> getNames(List<Map<String, String>> rows) { + Set<String> answer = new LinkedHashSet<String>(); + for (Map<String, String> row : rows) { + if (row.containsKey("name")) { + answer.add(row.get("name")); + } + } + return answer; + } + +} http://git-wip-us.apache.org/repos/asf/camel/blob/b23fbb46/platforms/camel-catalog/src/main/java/org/apache/camel/catalog/LanguageValidationResult.java ---------------------------------------------------------------------- diff --git a/platforms/camel-catalog/src/main/java/org/apache/camel/catalog/LanguageValidationResult.java b/platforms/camel-catalog/src/main/java/org/apache/camel/catalog/LanguageValidationResult.java new file mode 100644 index 0000000..4a977eb --- /dev/null +++ b/platforms/camel-catalog/src/main/java/org/apache/camel/catalog/LanguageValidationResult.java @@ -0,0 +1,65 @@ +/** + * 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.Serializable; + +/** + * Validation result of parsing a language expression or predicate + */ +public class LanguageValidationResult implements Serializable { + private final String text; + private String error; + private String shortError; + private int index; + + public LanguageValidationResult(String text) { + this.text = text; + } + + public String getText() { + return text; + } + + public boolean isSuccess() { + return error == null; + } + + public void setError(String error) { + this.error = error; + } + + public String getError() { + return error; + } + + public String getShortError() { + return shortError; + } + + public void setShortError(String shortError) { + this.shortError = shortError; + } + + public int getIndex() { + return index; + } + + public void setIndex(int index) { + this.index = index; + } +} http://git-wip-us.apache.org/repos/asf/camel/blob/b23fbb46/platforms/camel-catalog/src/main/java/org/apache/camel/catalog/RuntimeProvider.java ---------------------------------------------------------------------- diff --git a/platforms/camel-catalog/src/main/java/org/apache/camel/catalog/RuntimeProvider.java b/platforms/camel-catalog/src/main/java/org/apache/camel/catalog/RuntimeProvider.java new file mode 100644 index 0000000..fa4c3d6 --- /dev/null +++ b/platforms/camel-catalog/src/main/java/org/apache/camel/catalog/RuntimeProvider.java @@ -0,0 +1,93 @@ +/** + * 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.util.List; + +/** + * A pluggable strategy for chosen runtime to run Camel such as default, karaf, spring-boot, etc. + * This allows third party runtimes to provide their own provider, that can amend the catalog + * to match the runtime. For example spring-boot or karaf does not support all the default Camel components. + */ +public interface RuntimeProvider { + + /** + * Gets the {@link CamelCatalog} + */ + CamelCatalog getCamelCatalog(); + + /** + * Sets the {@link CamelCatalog} to use + */ + void setCamelCatalog(CamelCatalog camelCatalog); + + /** + * Name of provider such as <tt>default</tt>, <tt>karaf</tt>, <tt>spring-boot</tt> + */ + String getProviderName(); + + /** + * Maven group id of the runtime provider JAR dependency. + */ + String getProviderGroupId(); + + /** + * Maven artifact id of the runtime provider JAR dependency. + */ + String getProviderArtifactId(); + + /** + * Gets the directory where the component json files are stored in the catalog JAR file + */ + String getComponentJSonSchemaDirectory(); + + /** + * Gets the directory where the data format json files are stored in the catalog JAR file + */ + String getDataFormatJSonSchemaDirectory(); + + /** + * Gets the directory where the language json files are stored in the catalog JAR file + */ + String getLanguageJSonSchemaDirectory(); + + /** + * Gets the directory where the other (miscellaneous) json files are stored in the catalog JAR file + */ + String getOtherJSonSchemaDirectory(); + + /** + * Find all the component names from the Camel catalog supported by the provider + */ + List<String> findComponentNames(); + + /** + * Find all the data format names from the Camel catalog supported by the provider + */ + List<String> findDataFormatNames(); + + /** + * Find all the language names from the Camel catalog supported by the provider + */ + List<String> findLanguageNames(); + + /** + * Find all the other (miscellaneous) names from the Camel catalog supported by the provider + */ + List<String> findOtherNames(); + +} http://git-wip-us.apache.org/repos/asf/camel/blob/b23fbb46/platforms/camel-catalog/src/main/java/org/apache/camel/catalog/SimpleValidationResult.java ---------------------------------------------------------------------- diff --git a/platforms/camel-catalog/src/main/java/org/apache/camel/catalog/SimpleValidationResult.java b/platforms/camel-catalog/src/main/java/org/apache/camel/catalog/SimpleValidationResult.java new file mode 100644 index 0000000..5c86f23 --- /dev/null +++ b/platforms/camel-catalog/src/main/java/org/apache/camel/catalog/SimpleValidationResult.java @@ -0,0 +1,32 @@ +/** + * 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; + +/** + * To be backwards compatible, but favor using {@link LanguageValidationResult} instead. + */ +public class SimpleValidationResult extends LanguageValidationResult { + + public SimpleValidationResult(String text) { + super(text); + } + + public String getSimple() { + return getText(); + } + +} http://git-wip-us.apache.org/repos/asf/camel/blob/b23fbb46/platforms/camel-catalog/src/main/java/org/apache/camel/catalog/SuggestionStrategy.java ---------------------------------------------------------------------- diff --git a/platforms/camel-catalog/src/main/java/org/apache/camel/catalog/SuggestionStrategy.java b/platforms/camel-catalog/src/main/java/org/apache/camel/catalog/SuggestionStrategy.java new file mode 100644 index 0000000..057d372 --- /dev/null +++ b/platforms/camel-catalog/src/main/java/org/apache/camel/catalog/SuggestionStrategy.java @@ -0,0 +1,34 @@ +/** + * 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.util.Set; + +/** + * Strategy to provide suggestions for unknown endpoint options + */ +public interface SuggestionStrategy { + + /** + * Provides a list of valid option names for a did you mean function. + * + * @param names valid names + * @param unknownOption unknown option name + * @return a list of suggested names (did you mean) + */ + String[] suggestEndpointOptions(Set<String> names, String unknownOption); +} http://git-wip-us.apache.org/repos/asf/camel/blob/b23fbb46/platforms/camel-catalog/src/main/java/org/apache/camel/catalog/TimePatternConverter.java ---------------------------------------------------------------------- diff --git a/platforms/camel-catalog/src/main/java/org/apache/camel/catalog/TimePatternConverter.java b/platforms/camel-catalog/src/main/java/org/apache/camel/catalog/TimePatternConverter.java new file mode 100644 index 0000000..4ed843a --- /dev/null +++ b/platforms/camel-catalog/src/main/java/org/apache/camel/catalog/TimePatternConverter.java @@ -0,0 +1,120 @@ +/** + * 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.util.regex.Matcher; +import java.util.regex.Pattern; + +/** + * This class is a copy from camel-core so we can use it independent to validate uris with time patterns + */ +public final class TimePatternConverter { + private static final Pattern NUMBERS_ONLY_STRING_PATTERN = Pattern.compile("^[-]?(\\d)+$", Pattern.CASE_INSENSITIVE); + private static final Pattern HOUR_REGEX_PATTERN = Pattern.compile("((\\d)*(\\d))h(our(s)?)?", Pattern.CASE_INSENSITIVE); + private static final Pattern MINUTES_REGEX_PATTERN = Pattern.compile("((\\d)*(\\d))m(in(ute(s)?)?)?", Pattern.CASE_INSENSITIVE); + private static final Pattern SECONDS_REGEX_PATTERN = Pattern.compile("((\\d)*(\\d))s(ec(ond)?(s)?)?", Pattern.CASE_INSENSITIVE); + + /** + * Utility classes should not have a public constructor. + */ + private TimePatternConverter() { + } + + public static long toMilliSeconds(String source) throws IllegalArgumentException { + long milliseconds = 0; + boolean foundFlag = false; + + checkCorrectnessOfPattern(source); + Matcher matcher; + + matcher = createMatcher(NUMBERS_ONLY_STRING_PATTERN, source); + if (matcher.find()) { + // Note: This will also be used for regular numeric strings. + // This String -> long converter will be used for all strings. + milliseconds = Long.valueOf(source); + } else { + matcher = createMatcher(HOUR_REGEX_PATTERN, source); + if (matcher.find()) { + milliseconds = milliseconds + (3600000 * Long.valueOf(matcher.group(1))); + foundFlag = true; + } + + matcher = createMatcher(MINUTES_REGEX_PATTERN, source); + if (matcher.find()) { + long minutes = Long.valueOf(matcher.group(1)); + if ((minutes > 59) && foundFlag) { + throw new IllegalArgumentException("Minutes should contain a valid value between 0 and 59: " + source); + } + foundFlag = true; + milliseconds = milliseconds + (60000 * minutes); + } + + matcher = createMatcher(SECONDS_REGEX_PATTERN, source); + if (matcher.find()) { + long seconds = Long.valueOf(matcher.group(1)); + if ((seconds > 59) && foundFlag) { + throw new IllegalArgumentException("Seconds should contain a valid value between 0 and 59: " + source); + } + foundFlag = true; + milliseconds = milliseconds + (1000 * seconds); + } + + // No pattern matched... initiating fallback check and conversion (if required). + // The source at this point may contain illegal values or special characters + if (!foundFlag) { + milliseconds = Long.valueOf(source); + } + } + + return milliseconds; + } + + private static void checkCorrectnessOfPattern(String source) { + //replace only numbers once + Matcher matcher = createMatcher(NUMBERS_ONLY_STRING_PATTERN, source); + String replaceSource = matcher.replaceFirst(""); + + //replace hour string once + matcher = createMatcher(HOUR_REGEX_PATTERN, replaceSource); + if (matcher.find() && matcher.find()) { + throw new IllegalArgumentException("Hours should not be specified more then once: " + source); + } + replaceSource = matcher.replaceFirst(""); + + //replace minutes once + matcher = createMatcher(MINUTES_REGEX_PATTERN, replaceSource); + if (matcher.find() && matcher.find()) { + throw new IllegalArgumentException("Minutes should not be specified more then once: " + source); + } + replaceSource = matcher.replaceFirst(""); + + //replace seconds once + matcher = createMatcher(SECONDS_REGEX_PATTERN, replaceSource); + if (matcher.find() && matcher.find()) { + throw new IllegalArgumentException("Seconds should not be specified more then once: " + source); + } + replaceSource = matcher.replaceFirst(""); + + if (replaceSource.length() > 0) { + throw new IllegalArgumentException("Illegal characters: " + source); + } + } + + private static Matcher createMatcher(Pattern pattern, String source) { + return pattern.matcher(source); + } +} http://git-wip-us.apache.org/repos/asf/camel/blob/b23fbb46/platforms/camel-catalog/src/main/java/org/apache/camel/catalog/URISupport.java ---------------------------------------------------------------------- diff --git a/platforms/camel-catalog/src/main/java/org/apache/camel/catalog/URISupport.java b/platforms/camel-catalog/src/main/java/org/apache/camel/catalog/URISupport.java new file mode 100644 index 0000000..ea1ecd5 --- /dev/null +++ b/platforms/camel-catalog/src/main/java/org/apache/camel/catalog/URISupport.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.UnsupportedEncodingException; +import java.net.URI; +import java.net.URISyntaxException; +import java.net.URLDecoder; +import java.net.URLEncoder; +import java.util.ArrayList; +import java.util.Iterator; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; + +/** + * Copied from org.apache.camel.util.URISupport + */ +public final class URISupport { + + public static final String RAW_TOKEN_START = "RAW("; + public static final String RAW_TOKEN_END = ")"; + + private static final String CHARSET = "UTF-8"; + + private URISupport() { + // Helper class + } + + /** + * Normalizes the URI so unsafe characters is encoded + * + * @param uri the input uri + * @return as URI instance + * @throws URISyntaxException is thrown if syntax error in the input uri + */ + public static URI normalizeUri(String uri) throws URISyntaxException { + return new URI(UnsafeUriCharactersEncoder.encode(uri, true)); + } + + public static Map<String, Object> extractProperties(Map<String, Object> properties, String optionPrefix) { + Map<String, Object> rc = new LinkedHashMap<String, Object>(properties.size()); + + for (Iterator<Map.Entry<String, Object>> it = properties.entrySet().iterator(); it.hasNext();) { + Map.Entry<String, Object> entry = it.next(); + String name = entry.getKey(); + if (name.startsWith(optionPrefix)) { + Object value = properties.get(name); + name = name.substring(optionPrefix.length()); + rc.put(name, value); + it.remove(); + } + } + + return rc; + } + + /** + * Strips the query parameters from the uri + * + * @param uri the uri + * @return the uri without the query parameter + */ + public static String stripQuery(String uri) { + int idx = uri.indexOf('?'); + if (idx > -1) { + uri = uri.substring(0, idx); + } + return uri; + } + + /** + * Parses the query parameters of the uri (eg the query part). + * + * @param uri the uri + * @return the parameters, or an empty map if no parameters (eg never null) + * @throws URISyntaxException is thrown if uri has invalid syntax. + */ + public static Map<String, Object> parseParameters(URI uri) throws URISyntaxException { + String query = uri.getQuery(); + if (query == null) { + String schemeSpecificPart = uri.getSchemeSpecificPart(); + int idx = schemeSpecificPart.indexOf('?'); + if (idx < 0) { + // return an empty map + return new LinkedHashMap<String, Object>(0); + } else { + query = schemeSpecificPart.substring(idx + 1); + } + } else { + query = stripPrefix(query, "?"); + } + return parseQuery(query); + } + + /** + * Strips the prefix from the value. + * <p/> + * Returns the value as-is if not starting with the prefix. + * + * @param value the value + * @param prefix the prefix to remove from value + * @return the value without the prefix + */ + public static String stripPrefix(String value, String prefix) { + if (value != null && value.startsWith(prefix)) { + return value.substring(prefix.length()); + } + return value; + } + + /** + * Parses the query part of the uri (eg the parameters). + * <p/> + * The URI parameters will by default be URI encoded. However you can define a parameter + * values with the syntax: <tt>key=RAW(value)</tt> which tells Camel to not encode the value, + * and use the value as is (eg key=value) and the value has <b>not</b> been encoded. + * + * @param uri the uri + * @return the parameters, or an empty map if no parameters (eg never null) + * @throws URISyntaxException is thrown if uri has invalid syntax. + * @see #RAW_TOKEN_START + * @see #RAW_TOKEN_END + */ + public static Map<String, Object> parseQuery(String uri) throws URISyntaxException { + return parseQuery(uri, false); + } + + /** + * Parses the query part of the uri (eg the parameters). + * <p/> + * The URI parameters will by default be URI encoded. However you can define a parameter + * values with the syntax: <tt>key=RAW(value)</tt> which tells Camel to not encode the value, + * and use the value as is (eg key=value) and the value has <b>not</b> been encoded. + * + * @param uri the uri + * @param useRaw whether to force using raw values + * @return the parameters, or an empty map if no parameters (eg never null) + * @throws URISyntaxException is thrown if uri has invalid syntax. + * @see #RAW_TOKEN_START + * @see #RAW_TOKEN_END + */ + public static Map<String, Object> parseQuery(String uri, boolean useRaw) throws URISyntaxException { + // must check for trailing & as the uri.split("&") will ignore those + if (uri != null && uri.endsWith("&")) { + throw new URISyntaxException(uri, "Invalid uri syntax: Trailing & marker found. " + + "Check the uri and remove the trailing & marker."); + } + + if (isEmpty(uri)) { + // return an empty map + return new LinkedHashMap<String, Object>(0); + } + + // need to parse the uri query parameters manually as we cannot rely on splitting by &, + // as & can be used in a parameter value as well. + + try { + // use a linked map so the parameters is in the same order + Map<String, Object> rc = new LinkedHashMap<String, Object>(); + + boolean isKey = true; + boolean isValue = false; + boolean isRaw = false; + StringBuilder key = new StringBuilder(); + StringBuilder value = new StringBuilder(); + + // parse the uri parameters char by char + for (int i = 0; i < uri.length(); i++) { + // current char + char ch = uri.charAt(i); + // look ahead of the next char + char next; + if (i <= uri.length() - 2) { + next = uri.charAt(i + 1); + } else { + next = '\u0000'; + } + + // are we a raw value + isRaw = value.toString().startsWith(RAW_TOKEN_START); + + // if we are in raw mode, then we keep adding until we hit the end marker + if (isRaw) { + if (isKey) { + key.append(ch); + } else if (isValue) { + value.append(ch); + } + + // we only end the raw marker if its )& or at the end of the value + + boolean end = ch == RAW_TOKEN_END.charAt(0) && (next == '&' || next == '\u0000'); + if (end) { + // raw value end, so add that as a parameter, and reset flags + addParameter(key.toString(), value.toString(), rc, useRaw || isRaw); + key.setLength(0); + value.setLength(0); + isKey = true; + isValue = false; + isRaw = false; + // skip to next as we are in raw mode and have already added the value + i++; + } + continue; + } + + // if its a key and there is a = sign then the key ends and we are in value mode + if (isKey && ch == '=') { + isKey = false; + isValue = true; + isRaw = false; + continue; + } + + // the & denote parameter is ended + if (ch == '&') { + // parameter is ended, as we hit & separator + String aKey = key.toString(); + // the key may be a placeholder of options which we then do not know what is + boolean validKey = !aKey.startsWith("{{") && !aKey.endsWith("}}"); + if (validKey) { + addParameter(aKey, value.toString(), rc, useRaw || isRaw); + } + key.setLength(0); + value.setLength(0); + isKey = true; + isValue = false; + isRaw = false; + continue; + } + + // regular char so add it to the key or value + if (isKey) { + key.append(ch); + } else if (isValue) { + value.append(ch); + } + } + + // any left over parameters, then add that + if (key.length() > 0) { + String aKey = key.toString(); + // the key may be a placeholder of options which we then do not know what is + boolean validKey = !aKey.startsWith("{{") && !aKey.endsWith("}}"); + if (validKey) { + addParameter(aKey, value.toString(), rc, useRaw || isRaw); + } + } + + return rc; + + } catch (UnsupportedEncodingException e) { + URISyntaxException se = new URISyntaxException(e.toString(), "Invalid encoding"); + se.initCause(e); + throw se; + } + } + + @SuppressWarnings("unchecked") + private static void addParameter(String name, String value, Map<String, Object> map, boolean isRaw) throws UnsupportedEncodingException { + name = URLDecoder.decode(name, CHARSET); + if (!isRaw) { + // need to replace % with %25 + value = URLDecoder.decode(value.replaceAll("%", "%25"), CHARSET); + } + + // does the key already exist? + if (map.containsKey(name)) { + // yes it does, so make sure we can support multiple values, but using a list + // to hold the multiple values + Object existing = map.get(name); + List<String> list; + if (existing instanceof List) { + list = (List<String>) existing; + } else { + // create a new list to hold the multiple values + list = new ArrayList<String>(); + String s = existing != null ? existing.toString() : null; + if (s != null) { + list.add(s); + } + } + list.add(value); + map.put(name, list); + } else { + map.put(name, value); + } + } + + /** + * Assembles a query from the given map. + * + * @param options the map with the options (eg key/value pairs) + * @param ampersand to use & for Java code, and & for XML + * @return a query string with <tt>key1=value&key2=value2&...</tt>, or an empty string if there is no options. + * @throws URISyntaxException is thrown if uri has invalid syntax. + */ + public static String createQueryString(Map<String, String> options, String ampersand, boolean encode) throws URISyntaxException { + try { + if (options.size() > 0) { + StringBuilder rc = new StringBuilder(); + boolean first = true; + for (Object o : options.keySet()) { + if (first) { + first = false; + } else { + rc.append(ampersand); + } + + String key = (String) o; + Object value = options.get(key); + + // use the value as a String + String s = value != null ? value.toString() : null; + appendQueryStringParameter(key, s, rc, encode); + } + return rc.toString(); + } else { + return ""; + } + } catch (UnsupportedEncodingException e) { + URISyntaxException se = new URISyntaxException(e.toString(), "Invalid encoding"); + se.initCause(e); + throw se; + } + } + + private static void appendQueryStringParameter(String key, String value, StringBuilder rc, boolean encode) throws UnsupportedEncodingException { + if (encode) { + rc.append(URLEncoder.encode(key, CHARSET)); + } else { + rc.append(key); + } + // only append if value is not null + if (value != null) { + rc.append("="); + if (value.startsWith(RAW_TOKEN_START) && value.endsWith(RAW_TOKEN_END)) { + // do not encode RAW parameters + rc.append(value); + } else { + if (encode) { + rc.append(URLEncoder.encode(value, CHARSET)); + } else { + rc.append(value); + } + } + } + } + + /** + * Tests whether the value is <tt>null</tt> or an empty string. + * + * @param value the value, if its a String it will be tested for text length as well + * @return true if empty + */ + public static boolean isEmpty(Object value) { + return !isNotEmpty(value); + } + + /** + * Tests whether the value is <b>not</b> <tt>null</tt> or an empty string. + * + * @param value the value, if its a String it will be tested for text length as well + * @return true if <b>not</b> empty + */ + public static boolean isNotEmpty(Object value) { + if (value == null) { + return false; + } else if (value instanceof String) { + String text = (String) value; + return text.trim().length() > 0; + } else { + return true; + } + } + +} http://git-wip-us.apache.org/repos/asf/camel/blob/b23fbb46/platforms/camel-catalog/src/main/java/org/apache/camel/catalog/UnsafeUriCharactersEncoder.java ---------------------------------------------------------------------- diff --git a/platforms/camel-catalog/src/main/java/org/apache/camel/catalog/UnsafeUriCharactersEncoder.java b/platforms/camel-catalog/src/main/java/org/apache/camel/catalog/UnsafeUriCharactersEncoder.java new file mode 100644 index 0000000..563bac2 --- /dev/null +++ b/platforms/camel-catalog/src/main/java/org/apache/camel/catalog/UnsafeUriCharactersEncoder.java @@ -0,0 +1,206 @@ +/** + * 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.util.ArrayList; +import java.util.BitSet; +import java.util.List; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +/** + * Encoder for unsafe URI characters. + * <p/> + * A good source for details is <a href="http://en.wikipedia.org/wiki/Url_encode">wikipedia url encode</a> article. + */ +public final class UnsafeUriCharactersEncoder { + private static BitSet unsafeCharactersRfc1738; + private static BitSet unsafeCharactersHttp; + private static final char[] HEX_DIGITS = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', + 'D', 'E', 'F', 'a', 'b', 'c', 'd', 'e', 'f'}; + private static final Pattern RAW_PATTERN = Pattern.compile("RAW\\([^\\)]+\\)"); + + static { + unsafeCharactersRfc1738 = new BitSet(256); + unsafeCharactersRfc1738.set(' '); + unsafeCharactersRfc1738.set('"'); + unsafeCharactersRfc1738.set('<'); + unsafeCharactersRfc1738.set('>'); + unsafeCharactersRfc1738.set('#'); + unsafeCharactersRfc1738.set('%'); + unsafeCharactersRfc1738.set('{'); + unsafeCharactersRfc1738.set('}'); + unsafeCharactersRfc1738.set('|'); + unsafeCharactersRfc1738.set('\\'); + unsafeCharactersRfc1738.set('^'); + unsafeCharactersRfc1738.set('~'); + unsafeCharactersRfc1738.set('['); + unsafeCharactersRfc1738.set(']'); + unsafeCharactersRfc1738.set('`'); + } + + static { + unsafeCharactersHttp = new BitSet(256); + unsafeCharactersHttp.set(' '); + unsafeCharactersHttp.set('"'); + unsafeCharactersHttp.set('<'); + unsafeCharactersHttp.set('>'); + unsafeCharactersHttp.set('#'); + unsafeCharactersHttp.set('%'); + unsafeCharactersHttp.set('{'); + unsafeCharactersHttp.set('}'); + unsafeCharactersHttp.set('|'); + unsafeCharactersHttp.set('\\'); + unsafeCharactersHttp.set('^'); + unsafeCharactersHttp.set('~'); + unsafeCharactersHttp.set('`'); + } + + private UnsafeUriCharactersEncoder() { + // util class + } + + public static String encode(String s) { + return encode(s, unsafeCharactersRfc1738); + } + + public static String encodeHttpURI(String s) { + return encode(s, unsafeCharactersHttp); + } + + public static String encode(String s, BitSet unsafeCharacters) { + return encode(s, unsafeCharacters, false); + } + + public static String encode(String s, boolean checkRaw) { + return encode(s, unsafeCharactersRfc1738, checkRaw); + } + + public static String encodeHttpURI(String s, boolean checkRaw) { + return encode(s, unsafeCharactersHttp, checkRaw); + } + + private static List<Pair> checkRAW(String s) { + Matcher matcher = RAW_PATTERN.matcher(s); + List<Pair> answer = new ArrayList<Pair>(); + // Check all occurrences + while (matcher.find()) { + answer.add(new Pair(matcher.start(), matcher.end())); + } + return answer; + } + + private static boolean isRaw(int index, List<Pair> pairs) { + for (Pair pair : pairs) { + if (index < pair.left) { + return false; + } else { + if (index >= pair.left) { + if (index <= pair.right) { + return true; + } else { + continue; + } + } + } + } + return false; + } + + private static class Pair { + int left; + int right; + + Pair(int left, int right) { + this.left = left; + this.right = right; + } + } + + // Just skip the encode for isRAW part + public static String encode(String s, BitSet unsafeCharacters, boolean checkRaw) { + List<Pair> rawPairs; + if (checkRaw) { + rawPairs = checkRAW(s); + } else { + rawPairs = new ArrayList<Pair>(); + } + + int n = s == null ? 0 : s.length(); + if (n == 0) { + return s; + } + + // First check whether we actually need to encode + char chars[] = s.toCharArray(); + for (int i = 0;;) { + // just deal with the ascii character + if (chars[i] > 0 && chars[i] < 128) { + if (unsafeCharacters.get(chars[i])) { + break; + } + } + if (++i >= chars.length) { + return s; + } + } + + // okay there are some unsafe characters so we do need to encode + // see details at: http://en.wikipedia.org/wiki/Url_encode + StringBuilder sb = new StringBuilder(); + for (int i = 0; i < chars.length; i++) { + char ch = chars[i]; + if (ch > 0 && ch < 128 && unsafeCharacters.get(ch)) { + // special for % sign as it may be a decimal encoded value + if (ch == '%') { + char next = i + 1 < chars.length ? chars[i + 1] : ' '; + char next2 = i + 2 < chars.length ? chars[i + 2] : ' '; + + if (isHexDigit(next) && isHexDigit(next2) && !isRaw(i, rawPairs)) { + // its already encoded (decimal encoded) so just append as is + sb.append(ch); + } else { + // must escape then, as its an unsafe character + appendEscape(sb, (byte) ch); + } + } else { + // must escape then, as its an unsafe character + appendEscape(sb, (byte) ch); + } + } else { + sb.append(ch); + } + } + return sb.toString(); + } + + private static void appendEscape(StringBuilder sb, byte b) { + sb.append('%'); + sb.append(HEX_DIGITS[(b >> 4) & 0x0f]); + sb.append(HEX_DIGITS[(b >> 0) & 0x0f]); + } + + private static boolean isHexDigit(char ch) { + for (char hex : HEX_DIGITS) { + if (hex == ch) { + return true; + } + } + return false; + } + +} http://git-wip-us.apache.org/repos/asf/camel/blob/b23fbb46/platforms/camel-catalog/src/main/java/org/apache/camel/catalog/VersionHelper.java ---------------------------------------------------------------------- diff --git a/platforms/camel-catalog/src/main/java/org/apache/camel/catalog/VersionHelper.java b/platforms/camel-catalog/src/main/java/org/apache/camel/catalog/VersionHelper.java new file mode 100644 index 0000000..e1c69bc --- /dev/null +++ b/platforms/camel-catalog/src/main/java/org/apache/camel/catalog/VersionHelper.java @@ -0,0 +1,73 @@ +/** + * 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.InputStream; +import java.util.Properties; + +/** + * To get the version of this catalog. + */ +public class VersionHelper { + + private static volatile String version; + + public synchronized String getVersion() { + if (version != null) { + return version; + } + InputStream is = null; + // try to load from maven properties first + try { + Properties p = new Properties(); + is = getClass().getResourceAsStream("/META-INF/maven/org.apache.camel/camel-catalog/pom.properties"); + if (is != null) { + p.load(is); + version = p.getProperty("version", ""); + } + } catch (Exception e) { + // ignore + } finally { + if (is != null) { + try { + is.close(); + } catch (Exception e) { + // ignore + } + } + } + + // fallback to using Java API + if (version == null) { + Package aPackage = getClass().getPackage(); + if (aPackage != null) { + version = aPackage.getImplementationVersion(); + if (version == null) { + version = aPackage.getSpecificationVersion(); + } + } + } + + if (version == null) { + // we could not compute the version so use a blank + version = ""; + } + + return version; + } + +} http://git-wip-us.apache.org/repos/asf/camel/blob/b23fbb46/platforms/camel-catalog/src/main/java/org/apache/camel/catalog/VersionManager.java ---------------------------------------------------------------------- diff --git a/platforms/camel-catalog/src/main/java/org/apache/camel/catalog/VersionManager.java b/platforms/camel-catalog/src/main/java/org/apache/camel/catalog/VersionManager.java new file mode 100644 index 0000000..6f8aff4 --- /dev/null +++ b/platforms/camel-catalog/src/main/java/org/apache/camel/catalog/VersionManager.java @@ -0,0 +1,70 @@ +/** + * 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.InputStream; + +/** + * Strategy to manage and load Camel versions the catalog uses. + */ +public interface VersionManager { + + /** + * Gets the current loaded Camel version used by the catalog. + */ + String getLoadedVersion(); + + /** + * Attempt to load the Camel version to be used by the catalog. + * <p/> + * Loading the camel-catalog JAR of the given version of choice may require internet access + * to download the JAR from Maven central. You can pre download the JAR and install in a local + * Maven repository to avoid internet access for offline environments. + * + * @param version the Camel version such as <tt>2.17.1</tt> + * @return <tt>true</tt> if the version was loaded, <tt>false</tt> if not. + */ + boolean loadVersion(String version); + + /** + * Gets the current loaded runtime provider version used by the catalog. + */ + String getRuntimeProviderLoadedVersion(); + + /** + * Attempt to load the runtime provider version to be used by the catalog. + * <p/> + * Loading the runtime provider JAR of the given version of choice may require internet access + * to download the JAR from Maven central. You can pre download the JAR and install in a local + * Maven repository to avoid internet access for offline environments. + * + * @param groupId the runtime provider Maven groupId + * @param artifactId the runtime provider Maven artifactId + * @param version the runtime provider Maven version + * @return <tt>true</tt> if the version was loaded, <tt>false</tt> if not. + */ + boolean loadRuntimeProviderVersion(String groupId, String artifactId, String version); + + /** + * Returns an input stream for reading the specified resource from the loaded Catalog version. + * + * @param name the resource name + * @return the stream if found, or <tt>null</tt> if not found. + */ + InputStream getResourceAsStream(String name); + +}