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 2023/06/08 11:55:41 UTC
[camel] 03/03: CAMEL-19307: camel-yaml-io - dumping routes to yaml
This is an automated email from the ASF dual-hosted git repository.
davsclaus pushed a commit to branch yaml4
in repository https://gitbox.apache.org/repos/asf/camel.git
commit 67361bd2a3d3e0dc1f0467d9f795f208667e4bdc
Author: Claus Ibsen <cl...@gmail.com>
AuthorDate: Thu Jun 8 13:55:12 2023 +0200
CAMEL-19307: camel-yaml-io - dumping routes to yaml
---
core/camel-xml-io/src/test/resources/loop.xml | 3 +-
core/camel-yaml-io/pom.xml | 5 +
.../org/apache/camel/yaml/LwModelToYAMLDumper.java | 3 +-
.../java/org/apache/camel/yaml/io/EipNode.java | 248 +----------
.../apache/camel/yaml/io/NewJacksonYamlWriter.java | 355 ----------------
.../org/apache/camel/yaml/io/NewYamlWriter.java | 280 -------------
.../java/org/apache/camel/yaml/io/YamlWriter.java | 451 +++++++++++++++------
.../java/org/apache/camel/yaml/out/BaseWriter.java | 6 +-
.../org/apache/camel/yaml/out/ModelWriterTest.java | 56 +++
.../org/apache/camel/yaml/out/XmlToYamlTest.java | 78 ++++
core/camel-yaml-io/src/test/resources/route8.yaml | 20 +
core/camel-yaml-io/src/test/resources/route9.yaml | 9 +
12 files changed, 510 insertions(+), 1004 deletions(-)
diff --git a/core/camel-xml-io/src/test/resources/loop.xml b/core/camel-xml-io/src/test/resources/loop.xml
index 9aa92142370..8c0e7645a5d 100644
--- a/core/camel-xml-io/src/test/resources/loop.xml
+++ b/core/camel-xml-io/src/test/resources/loop.xml
@@ -19,9 +19,10 @@
-->
<routes id="camel" xmlns="http://camel.apache.org/schema/spring">
<route>
- <from uri="direct:a"/>
+ <from uri="timer:a"/>
<loop>
<constant>8</constant>
+ <log message="looping"/>
</loop>
</route>
</routes>
diff --git a/core/camel-yaml-io/pom.xml b/core/camel-yaml-io/pom.xml
index 2cb6a70214a..df414c9b87f 100644
--- a/core/camel-yaml-io/pom.xml
+++ b/core/camel-yaml-io/pom.xml
@@ -57,6 +57,11 @@
</dependency>
<!-- testing -->
+ <dependency>
+ <groupId>org.apache.camel</groupId>
+ <artifactId>camel-xml-io</artifactId>
+ <scope>test</scope>
+ </dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter</artifactId>
diff --git a/core/camel-yaml-io/src/main/java/org/apache/camel/yaml/LwModelToYAMLDumper.java b/core/camel-yaml-io/src/main/java/org/apache/camel/yaml/LwModelToYAMLDumper.java
index daa26c58d88..d111e5c27b1 100644
--- a/core/camel-yaml-io/src/main/java/org/apache/camel/yaml/LwModelToYAMLDumper.java
+++ b/core/camel-yaml-io/src/main/java/org/apache/camel/yaml/LwModelToYAMLDumper.java
@@ -53,6 +53,7 @@ import static org.apache.camel.model.ProcessorDefinitionHelper.filterTypeInOutpu
*/
@JdkService(ModelToYAMLDumper.FACTORY)
public class LwModelToYAMLDumper implements ModelToYAMLDumper {
+
@Override
public String dumpModelAsYaml(CamelContext context, NamedNode definition) throws Exception {
return dumpModelAsYaml(context, definition, false, false);
@@ -208,7 +209,7 @@ public class LwModelToYAMLDumper implements ModelToYAMLDumper {
/**
* If the route has been built with endpoint-dsl, then the model will not have uri set which then cannot be included
- * in the JAXB model dump
+ * in the model dump
*/
@SuppressWarnings("rawtypes")
private static void resolveEndpointDslUris(RouteDefinition route) {
diff --git a/core/camel-yaml-io/src/main/java/org/apache/camel/yaml/io/EipNode.java b/core/camel-yaml-io/src/main/java/org/apache/camel/yaml/io/EipNode.java
index 5abe8e72171..df70acc4d9f 100644
--- a/core/camel-yaml-io/src/main/java/org/apache/camel/yaml/io/EipNode.java
+++ b/core/camel-yaml-io/src/main/java/org/apache/camel/yaml/io/EipNode.java
@@ -22,7 +22,6 @@ import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
-import org.apache.camel.util.StringHelper;
import org.apache.camel.util.json.JsonArray;
import org.apache.camel.util.json.JsonObject;
@@ -31,7 +30,7 @@ import org.apache.camel.util.json.JsonObject;
*/
class EipNode {
- private String name;
+ private final String name;
private final EipNode parent;
private final boolean output;
private final boolean expression;
@@ -48,10 +47,6 @@ class EipNode {
this.expression = expression;
}
- public void clearName() {
- this.name = null;
- }
-
public String getName() {
return name;
}
@@ -126,231 +121,15 @@ class EipNode {
properties.put(key, value);
}
- public String dump(int level, int spaces, String lineSeparator, boolean steps) {
- if ("choice".equals(name)) {
- // special for choice EIP
- return dumpChoice(level, spaces, lineSeparator);
- }
-
- StringBuilder sb = new StringBuilder();
- String n = name;
-
- if (n != null && steps) {
- n = "- " + name;
- }
- if (n != null) {
- sb.append(padString(spaces, level)).append(n).append(":");
- if (n.startsWith("- ")) {
- level++;
- }
- }
- if (expressions != null) {
- level++;
- for (EipNode child : expressions) {
- if (child.getExpressions().isEmpty()) {
- sb.append(lineSeparator).append(padString(spaces, level)).append("expression:");
- level++;
- }
- String text = child.dumpExpression(level, spaces, lineSeparator);
- sb.append("\n");
- sb.append(text);
- if (child.getExpressions().isEmpty()) {
- level--;
- }
- }
- level--;
- }
- if (text != null) {
- String escaped = escape(spaces, level + 2, lineSeparator, text);
- sb.append(lineSeparator).append(padString(spaces, level + 1)).append("expression: ").append(escaped);
- }
- if (properties != null) {
- for (Map.Entry<String, Object> entry : properties.entrySet()) {
- String escaped = escape(spaces, level + 1, lineSeparator, entry.getValue());
- String line = String.format("%s: %s", entry.getKey(), escaped);
- sb.append(lineSeparator).append(padString(spaces, level + 1)).append(line);
- }
- }
- if (input != null) {
- String text = input.dump(level + 1, spaces, lineSeparator, false);
- sb.append(lineSeparator);
- sb.append(text);
- level++;
- }
- if (outputs != null) {
- level++;
- sb.append("\n").append(padString(spaces, level)).append("steps:");
- level++;
- for (EipNode child : outputs) {
- String text = child.dump(level, spaces, lineSeparator, true);
- sb.append("\n");
- sb.append(text);
- }
- }
- return sb.toString();
- }
-
- public String dumpExpression(int level, int spaces, String lineSeparator) {
- StringBuilder sb = new StringBuilder();
- String n = name;
- sb.append(padString(spaces, level)).append(n).append(":");
- if (n.startsWith("- ")) {
- level++;
- }
- if (text != null) {
- String escaped = escape(spaces, level + 1, lineSeparator, text);
- sb.append(lineSeparator).append(padString(spaces, level + 1)).append("expression: ").append(escaped);
- }
- if (properties != null) {
- for (Map.Entry<String, Object> entry : properties.entrySet()) {
- String escaped = escape(spaces, level + 1, lineSeparator, entry.getValue());
- String line = String.format("%s: %s", entry.getKey(), escaped);
- sb.append(lineSeparator).append(padString(spaces, level + 1)).append(line);
- }
- }
- if (expressions != null) {
- level++;
- for (EipNode child : expressions) {
- String text = child.dumpExpression(level, spaces, lineSeparator);
- sb.append("\n");
- sb.append(text);
- }
- }
- return sb.toString();
- }
-
- public String dumpChoice(int level, int spaces, String lineSeparator) {
- StringBuilder sb = new StringBuilder();
-
- sb.append(padString(spaces, level)).append("- choice").append(":");
- level++;
- if (properties != null) {
- for (Map.Entry<String, Object> entry : properties.entrySet()) {
- String escaped = escape(spaces, level + 1, lineSeparator, entry.getValue());
- String line = String.format("%s: %s", entry.getKey(), escaped);
- sb.append(lineSeparator).append(padString(spaces, level + 1)).append(line);
- }
- }
- // sort so otherwise is last
- if (outputs != null) {
- outputs.sort((o1, o2) -> {
- if ("otherwise".equals(o1.name)) {
- return 1;
- } else if ("otherwise".equals(o2.name)) {
- return -1;
- }
- return 0;
- });
- // when
- for (int i = 0; i < outputs.size(); i++) {
- EipNode child = outputs.get(i);
- if (i == 0 || "otherwise".equals(child.name)) {
- level++;
- sb.append(lineSeparator).append(padString(spaces, level)).append(child.name).append(":");
- level++;
- }
- child.clearName();
- String text = child.dump(level, spaces, lineSeparator, true);
- sb.append("\n");
- sb.append(text);
- }
- }
- return sb.toString();
- }
-
- private static String padString(int spaces, int level) {
- return " ".repeat(spaces).repeat(level);
- }
-
@Override
public String toString() {
return name;
}
- private static String escape(int spaces, int level, String lineSeparator, Object value) {
- // escapes the yaml
- StringBuilder sb = new StringBuilder();
-
- String text = value.toString();
-
- if (text.contains(lineSeparator)) {
- // multi lined values
- String[] lines = text.split(lineSeparator);
- sb.append("|-");
- for (String line : lines) {
- sb.append(lineSeparator).append(padString(spaces, level + 1)).append(line);
- }
- } else if (text.contains("\\") || text.contains("//")) {
- // using slash/backslashes
- sb.append(">-");
- sb.append(lineSeparator).append(padString(spaces, level + 1)).append(text);
- } else if (StringHelper.isSingleQuoted(text)) {
- // single quotes should be escaped to triple
- sb.append("''");
- sb.append(text);
- sb.append("''");
- } else if (StringHelper.isDoubleQuoted(text)) {
- // double quotes should be enclosed by single quote
- sb.append("'");
- // single quotes should be escaped
- text = text.replace("'", "''");
- sb.append(text);
- sb.append("'");
- } else if (valueHasQuotableChar(text)) {
- // should be enclosed by single quote
- sb.append("'");
- // single quotes should be escaped
- text = text.replace("'", "''");
- sb.append(text);
- sb.append("'");
- } else {
- sb.append(text);
- }
-
- return sb.toString();
- }
-
/**
- * As per YAML <a href="https://yaml.org/spec/1.2/spec.html#id2788859">Plain Style</a>unquoted strings are
- * restricted to a reduced charset and must be quoted in case they contain one of the following characters or
- * character combinations.
+ * Converts this node to JSon
*/
- private static boolean valueHasQuotableChar(String inputStr) {
- // https://github.com/FasterXML/jackson-dataformats-text/blob/2.16/yaml/src/main/java/com/fasterxml/jackson/dataformat/yaml/util/StringQuotingChecker.java
- final int end = inputStr.length();
- for (int i = 0; i < end; ++i) {
- switch (inputStr.charAt(i)) {
- case '[':
- case ']':
- case '{':
- case '}':
- case ',':
- return true;
- case '#':
- // [dataformats-text#201]: limit quoting with MINIMIZE_QUOTES
- if (i > 0) {
- char d = inputStr.charAt(i - 1);
- if (' ' == d || '\t' == d) {
- return true;
- }
- }
- break;
- case ':':
- // [dataformats-text#201]: limit quoting with MINIMIZE_QUOTES
- if (i < (end - 1)) {
- char d = inputStr.charAt(i + 1);
- if (' ' == d || '\t' == d) {
- return true;
- }
- }
- break;
- default:
- }
- }
- return false;
- }
-
- public JsonObject asJsonObject() {
+ JsonObject asJsonObject() {
JsonObject answer = new JsonObject();
if (properties != null) {
@@ -376,7 +155,6 @@ class EipNode {
}
}
}
-
if (outputs != null) {
// sort so otherwise is last
outputs.sort((o1, o2) -> {
@@ -387,14 +165,20 @@ class EipNode {
}
return 0;
});
- JsonArray arr = new JsonArray();
- for (EipNode o : outputs) {
- JsonObject r = o.asJsonObject();
- JsonObject wrap = new JsonObject();
- wrap.put(o.getName(), r);
- arr.add(wrap);
+ if (("marshal".equals(name) || "unmarshal".equals(name)) && outputs.size() == 1) {
+ EipNode o = outputs.get(0);
+ JsonObject jo = o.asJsonObject();
+ answer.put(o.getName(), jo);
+ } else {
+ JsonArray arr = new JsonArray();
+ for (EipNode o : outputs) {
+ JsonObject r = o.asJsonObject();
+ JsonObject wrap = new JsonObject();
+ wrap.put(o.getName(), r);
+ arr.add(wrap);
+ }
+ answer.put("steps", arr);
}
- answer.put("steps", arr);
}
return answer;
diff --git a/core/camel-yaml-io/src/main/java/org/apache/camel/yaml/io/NewJacksonYamlWriter.java b/core/camel-yaml-io/src/main/java/org/apache/camel/yaml/io/NewJacksonYamlWriter.java
deleted file mode 100644
index 8fb9cfb4be8..00000000000
--- a/core/camel-yaml-io/src/main/java/org/apache/camel/yaml/io/NewJacksonYamlWriter.java
+++ /dev/null
@@ -1,355 +0,0 @@
-/*
- * 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.yaml.io;
-
-import java.io.IOException;
-import java.io.Writer;
-import java.util.ArrayList;
-import java.util.Collection;
-import java.util.List;
-import java.util.Map;
-import java.util.Stack;
-import java.util.StringJoiner;
-
-import com.fasterxml.jackson.databind.JsonNode;
-import com.fasterxml.jackson.databind.ObjectMapper;
-import com.fasterxml.jackson.dataformat.yaml.YAMLGenerator;
-import com.fasterxml.jackson.dataformat.yaml.YAMLMapper;
-import org.apache.camel.catalog.impl.DefaultRuntimeCamelCatalog;
-import org.apache.camel.tooling.model.BaseOptionModel;
-import org.apache.camel.tooling.model.EipModel;
-import org.apache.camel.util.json.JsonArray;
-import org.apache.camel.util.json.JsonObject;
-
-public class NewJacksonYamlWriter {
-
- private final Writer writer;
- private final DefaultRuntimeCamelCatalog catalog;
- private final List<EipModel> roots = new ArrayList<>();
- private final Stack<EipModel> models = new Stack<>();
- private String expression;
-
- public NewJacksonYamlWriter(Writer writer) throws IOException {
- this.writer = writer;
- this.catalog = new DefaultRuntimeCamelCatalog();
- this.catalog.setCaching(false); // turn cache off as we store state per node
- this.catalog.setJSonSchemaResolver(new ModelJSonSchemaResolver());
- this.catalog.start();
- }
-
- public void startElement(String name) throws IOException {
- EipModel model = catalog.eipModel(name);
- if (model == null) {
- // not an EIP model
- return;
- }
- EipModel parent = models.isEmpty() ? null : models.peek();
- model.getMetadata().put("_parent", parent);
- models.push(model);
- if (parent == null) {
- // its a root element
- roots.add(model);
- }
- }
-
- public void startExpressionElement(String name) throws IOException {
- // currently building an expression
- this.expression = name;
- }
-
- public void endExpressionElement(String name) throws IOException {
- // expression complete, back to normal mode
- this.expression = null;
- }
-
- public void endElement(String name) throws IOException {
- EipModel model = catalog.eipModel(name);
- if (model == null) {
- // not an EIP model
- return;
- }
-
- EipModel last = models.isEmpty() ? null : models.peek();
- if (last != null && isLanguage(last)) {
- if (!models.isEmpty()) {
- models.pop();
- }
- // okay we ended a language which we need to set on a parent EIP
- EipModel parent = models.isEmpty() ? null : models.peek();
- if (parent != null) {
- String key = expressionName(parent, expression);
- if (key != null) {
- parent.getMetadata().put(key, last);
- }
- }
- return;
- }
-
- if (last != null) {
- if (!models.isEmpty()) {
- models.pop();
- }
- // is this input/output on the parent
- EipModel parent = models.isEmpty() ? null : models.peek();
- if (parent != null) {
- if ("from".equals(name) && parent.isInput()) {
- // only set input once
- parent.getMetadata().put("_input", last);
- } else if ("choice".equals(parent.getName())) {
- // special for choice/doCatch/doFinally
- setMetadata(parent, name, last);
- } else if (parent.isOutput()) {
- List<EipModel> list = (List<EipModel>) parent.getMetadata().get("_output");
- if (list == null) {
- list = new ArrayList<>();
- parent.getMetadata().put("_output", list);
- }
- list.add(last);
- }
- }
- }
-
- if (models.isEmpty()) {
- // we are done
- writer.write(toYaml());
- }
- }
-
- public void writeText(String name, String text) throws IOException {
- EipModel last = models.isEmpty() ? null : models.peek();
- if (last != null) {
- // special as writeText can be used for list of string values
- setMetadata(last, name, text);
- }
- }
-
- public void writeValue(String value) throws IOException {
- EipModel last = models.isEmpty() ? null : models.peek();
- if (last != null) {
- String key = valueName(last);
- if (key != null) {
- last.getMetadata().put(key, value);
- }
- }
- }
-
- public void addAttribute(String name, Object value) throws IOException {
- EipModel last = models.isEmpty() ? null : models.peek();
- if (last != null) {
- last.getMetadata().put(name, value);
- }
- }
-
- @SuppressWarnings("unchecked")
- private static void setMetadata(EipModel model, String name, Object value) {
- // special for choice
- boolean array = isArray(model, name);
- if (array) {
- List list = (List) model.getMetadata().get(name);
- if (list == null) {
- list = new ArrayList<>();
- model.getMetadata().put(name, list);
- }
- list.add(value);
- } else {
- model.getMetadata().put(name, value);
- }
- }
-
- private static String valueName(EipModel model) {
- return model.getOptions().stream()
- .filter(o -> "value".equals(o.getKind()))
- .map(BaseOptionModel::getName)
- .findFirst().orElse(null);
- }
-
- private static String expressionName(EipModel model, String name) {
- return model.getOptions().stream()
- .filter(o -> "expression".equals(o.getKind()))
- .map(BaseOptionModel::getName)
- .filter(oName -> name == null || oName.equalsIgnoreCase(name))
- .findFirst().orElse(null);
- }
-
- private static boolean isArray(EipModel model, String name) {
- return model.getOptions().stream()
- .filter(o -> o.getName().equalsIgnoreCase(name))
- .map(o -> "array".equals(o.getType()))
- .findFirst().orElse(false);
- }
-
- private static boolean isLanguage(EipModel model) {
- return model.getJavaType().startsWith("org.apache.camel.model.language");
- }
-
- protected EipNode asExpressionNode(EipModel model, String name) {
- EipNode node = new EipNode(name, null, false, true);
- doAsNode(model, node);
- return node;
- }
-
- protected EipNode asNode(EipModel model) {
- EipNode node = new EipNode(model.getName(), null, false, false);
- doAsNode(model, node);
- return node;
- }
-
- protected void doAsNode(EipModel model, EipNode node) {
- for (Map.Entry<String, Object> entry : model.getMetadata().entrySet()) {
- String key = entry.getKey();
- if ("_input".equals(key)) {
- EipModel m = (EipModel) entry.getValue();
- node.setInput(asNode(m));
- } else if ("_output".equals(key)) {
- List<EipModel> list = (List) entry.getValue();
- for (EipModel m : list) {
- node.addOutput(asNode(m));
- }
- } else if ("choice".equals(node.getName()) && "otherwise".equals(key)) {
- EipModel other = (EipModel) entry.getValue();
- node.addOutput(asNode(other));
- } else if ("choice".equals(node.getName()) && "when".equals(key)) {
- Object v = entry.getValue();
- if (v instanceof List) {
- // can be a list in choice
- List<EipModel> list = (List) v;
- for (EipModel m : list) {
- node.addOutput(asNode(m));
- }
- } else {
- node.addOutput(asNode((EipModel) v));
- }
- } else {
- boolean skip = key.startsWith("_") || key.equals("customId");
- if (skip) {
- continue;
- }
- String exp = null;
- if (!isLanguage(model)) {
- // special for expressions that are a property where we need to use expression name as key
- exp = expressionName(model, key);
- }
- Object v = entry.getValue();
- if (v instanceof EipModel) {
- EipModel m = (EipModel) entry.getValue();
- if (exp == null || "expression".equals(exp)) {
- v = asExpressionNode(m, m.getName());
- } else {
- v = asExpressionNode(m, exp);
- }
- }
- if (exp != null && v instanceof EipNode) {
- node.addExpression((EipNode) v);
- } else {
- node.addProperty(key, v);
- if ("expression".equals(key)) {
- node.addProperty("language", model.getName());
- }
- }
- }
- }
- }
-
- public String toYaml() {
- JsonArray arr = transformToJson(roots);
-
- // parse JSON
- try {
- JsonNode jsonNodeTree = new ObjectMapper().readTree(arr.toJson());
- // save it as YAML
- YAMLMapper mapper = new YAMLMapper();
- mapper.disable(YAMLGenerator.Feature.WRITE_DOC_START_MARKER);
- mapper.enable(YAMLGenerator.Feature.MINIMIZE_QUOTES);
- mapper.enable(YAMLGenerator.Feature.INDENT_ARRAYS_WITH_INDICATOR);
- String jsonAsYaml = mapper.writeValueAsString(jsonNodeTree);
- // strip leading yaml indent of 2 spaces (because INDENT_ARRAYS_WITH_INDICATOR is enabled)
- StringJoiner sj = new StringJoiner("\n");
- for (String line : jsonAsYaml.split("\n")) {
- if (line.startsWith(" ")) {
- line = line.substring(2);
- }
- sj.add(line);
- }
- sj.add(""); // end with empty line
- jsonAsYaml = sj.toString();
- return jsonAsYaml;
- } catch (Exception e) {
- throw new RuntimeException(e);
- }
- }
-
- protected JsonArray transformToJson(List<EipModel> models) {
- JsonArray arr = new JsonArray();
- for (EipModel model : models) {
- JsonObject jo = asJSonNode(model);
- arr.add(jo);
- }
- return arr;
- }
-
- @SuppressWarnings("unchecked")
- protected JsonObject asJSonNode(EipModel model) {
- JsonObject answer = new JsonObject();
- JsonObject jo = new JsonObject();
- answer.put(model.getName(), jo);
-
- for (Map.Entry<String, Object> entry : model.getMetadata().entrySet()) {
- String key = entry.getKey();
- boolean skip = key.equals("customId");
- if (skip) {
- continue;
- }
- Object value = entry.getValue();
- if (value != null) {
- if (value instanceof Collection<?>) {
- Collection<?> col = (Collection<?>) value;
- List list = new ArrayList<>();
- for (Object v : col) {
- Object r = v;
- if (r instanceof EipModel) {
- EipNode en = asNode((EipModel) r);
- value = en.asJsonObject();
- JsonObject wrap = new JsonObject();
- wrap.put(en.getName(), value);
- r = wrap;
- }
- list.add(r);
- }
- if ("_output".equals(key)) {
- key = "steps";
- }
- // special with "from" where outputs needs to be embedded
- if (jo.containsKey("from")) {
- jo = jo.getMap("from");
- }
- jo.put(key, list);
- } else {
- if (value instanceof EipModel) {
- EipNode r = asNode((EipModel) value);
- value = r.asJsonObject();
- jo.put(r.getName(), value);
- } else {
- jo.put(key, value);
- }
- }
- }
- }
-
- return answer;
- }
-
-}
diff --git a/core/camel-yaml-io/src/main/java/org/apache/camel/yaml/io/NewYamlWriter.java b/core/camel-yaml-io/src/main/java/org/apache/camel/yaml/io/NewYamlWriter.java
deleted file mode 100644
index 6d760d5ba37..00000000000
--- a/core/camel-yaml-io/src/main/java/org/apache/camel/yaml/io/NewYamlWriter.java
+++ /dev/null
@@ -1,280 +0,0 @@
-/*
- * 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.yaml.io;
-
-import java.io.IOException;
-import java.io.Writer;
-import java.util.ArrayList;
-import java.util.List;
-import java.util.Map;
-import java.util.Stack;
-
-import org.apache.camel.catalog.impl.DefaultRuntimeCamelCatalog;
-import org.apache.camel.tooling.model.BaseOptionModel;
-import org.apache.camel.tooling.model.EipModel;
-
-public class NewYamlWriter {
-
- // doTry / doCatch (special)
- // circuitBreaker (special)
-
- private final Writer writer;
- private final int spaces;
- private final String lineSeparator;
- private final DefaultRuntimeCamelCatalog catalog;
- private final List<EipModel> roots = new ArrayList<>();
- private final Stack<EipModel> models = new Stack<>();
- private String expression;
-
- /**
- * @param writer not null
- */
- public NewYamlWriter(Writer writer) throws IOException {
- this(writer, 2, null);
- }
-
- /**
- * @param writer not null
- * @param spaces number of spaces to indent
- * @param lineSeparator could be null, but the normal way is valid line separator ("\n" on UNIX).
- */
- public NewYamlWriter(Writer writer, int spaces, String lineSeparator) throws IOException {
- this.writer = writer;
- this.spaces = spaces;
- this.lineSeparator = validateLineSeparator(lineSeparator);
- this.catalog = new DefaultRuntimeCamelCatalog();
- this.catalog.setCaching(false); // turn cache off as we store state per node
- this.catalog.setJSonSchemaResolver(new ModelJSonSchemaResolver());
- this.catalog.start();
- }
-
- private static String validateLineSeparator(String lineSeparator) {
- String ls = lineSeparator != null ? lineSeparator : System.lineSeparator();
- if (!(ls.equals("\n") || ls.equals("\r") || ls.equals("\r\n"))) {
- throw new IllegalArgumentException("Requested line separator is invalid.");
- }
- return ls;
- }
-
- public void startElement(String name) throws IOException {
- EipModel model = catalog.eipModel(name);
- if (model != null) {
- EipModel parent = models.isEmpty() ? null : models.peek();
- model.getMetadata().put("_parent", parent);
- models.push(model);
- if (parent == null) {
- // its a root element
- roots.add(model);
- }
- }
- }
-
- public void startExpressionElement(String name) throws IOException {
- // currently building an expression
- this.expression = name;
- }
-
- public void endExpressionElement(String name) throws IOException {
- // expression complete, back to normal mode
- this.expression = null;
- }
-
- public void endElement(String name) throws IOException {
- EipModel last = models.isEmpty() ? null : models.peek();
- if (last != null && isLanguage(last)) {
- if (!models.isEmpty()) {
- models.pop();
- }
- // okay we ended a language which we need to set on a parent EIP
- EipModel parent = models.isEmpty() ? null : models.peek();
- if (parent != null) {
- String key = expressionName(parent, expression);
- if (key != null) {
- parent.getMetadata().put(key, last);
- }
- }
- return;
- }
-
- if (last != null) {
- if (!models.isEmpty()) {
- models.pop();
- }
- // is this input/output on the parent
- EipModel parent = models.isEmpty() ? null : models.peek();
- if (parent != null) {
- if ("from".equals(name) && parent.isInput()) {
- // only set input once
- parent.getMetadata().put("_input", last);
- } else if ("choice".equals(parent.getName())) {
- // special for choice/doCatch/doFinally
- setMetadata(parent, name, last);
- } else if (parent.isOutput()) {
- List<EipModel> list = (List<EipModel>) parent.getMetadata().get("_output");
- if (list == null) {
- list = new ArrayList<>();
- parent.getMetadata().put("_output", list);
- }
- list.add(last);
- }
- }
- }
-
- if (models.isEmpty()) {
- // we are done
- writer.write(toYaml());
- }
- }
-
- public void writeText(String name, String text) throws IOException {
- EipModel last = models.isEmpty() ? null : models.peek();
- if (last != null) {
- // special as writeText can be used for list of string values
- setMetadata(last, name, text);
- }
- }
-
- public void writeValue(String value) throws IOException {
- EipModel last = models.isEmpty() ? null : models.peek();
- if (last != null) {
- String key = valueName(last);
- if (key != null) {
- last.getMetadata().put(key, value);
- }
- }
- }
-
- public void addAttribute(String name, Object value) throws IOException {
- EipModel last = models.isEmpty() ? null : models.peek();
- if (last != null) {
- last.getMetadata().put(name, value);
- }
- }
-
- @SuppressWarnings("unchecked")
- private static void setMetadata(EipModel model, String name, Object value) {
- // special for choice
- boolean array = isArray(model, name);
- if (array) {
- List list = (List<EipModel>) model.getMetadata().get(name);
- if (list == null) {
- list = new ArrayList<>();
- model.getMetadata().put(name, list);
- }
- list.add(value);
- } else {
- model.getMetadata().put(name, value);
- }
- }
-
- private static String valueName(EipModel model) {
- return model.getOptions().stream()
- .filter(o -> "value".equals(o.getKind()))
- .map(BaseOptionModel::getName)
- .findFirst().orElse(null);
- }
-
- private static String expressionName(EipModel model, String name) {
- return model.getOptions().stream()
- .filter(o -> "expression".equals(o.getKind()))
- .map(BaseOptionModel::getName)
- .filter(oName -> name == null || oName.equalsIgnoreCase(name))
- .findFirst().orElse(null);
- }
-
- private static boolean isArray(EipModel model, String name) {
- return model.getOptions().stream()
- .filter(o -> o.getName().equalsIgnoreCase(name))
- .map(o -> "array".equals(o.getType()))
- .findFirst().orElse(false);
- }
-
- private static boolean isLanguage(EipModel model) {
- return model.getJavaType().startsWith("org.apache.camel.model.language");
- }
-
- protected List<EipNode> transformToNodes(List<EipModel> models) {
- List<EipNode> nodes = new ArrayList<>();
- for (EipModel model : models) {
- EipNode node = asNode(model);
- nodes.add(node);
- }
- return nodes;
- }
-
- protected EipNode asExpressionNode(EipModel model, String name) {
- EipNode node = new EipNode(name, null, false, true);
- doAsNode(model, node);
- return node;
- }
-
- protected EipNode asNode(EipModel model) {
- EipNode node = new EipNode(model.getName(), null, false, false);
- doAsNode(model, node);
- return node;
- }
-
- protected void doAsNode(EipModel model, EipNode node) {
- for (Map.Entry<String, Object> entry : model.getMetadata().entrySet()) {
- String key = entry.getKey();
- if ("_input".equals(key)) {
- EipModel m = (EipModel) entry.getValue();
- node.setInput(asNode(m));
- } else if ("_output".equals(key)) {
- List<EipModel> list = (List) entry.getValue();
- for (EipModel m : list) {
- node.addOutput(asNode(m));
- }
- } else {
- boolean skip = key.startsWith("_") || key.equals("customId");
- if (skip) {
- continue;
- }
- String exp = null;
- if (!isLanguage(model)) {
- // special for expressions that are a property where we need to use expression name as key
- exp = expressionName(model, key);
- }
- Object v = entry.getValue();
- if (v instanceof EipModel) {
- EipModel m = (EipModel) entry.getValue();
- if (exp == null || "expression".equals(exp)) {
- v = asExpressionNode(m, m.getName());
- } else {
- v = asExpressionNode(m, exp);
- }
- }
- if (exp != null && v instanceof EipNode) {
- node.addExpression((EipNode) v);
- } else {
- node.addProperty(key, v);
- }
- }
- }
- }
-
- public String toYaml() {
- StringBuilder sb = new StringBuilder();
- for (EipNode node : transformToNodes(roots)) {
- String s = node.dump(0, spaces, lineSeparator, true);
- sb.append(s);
- sb.append(lineSeparator);
- }
- return sb.toString();
- }
-
-}
diff --git a/core/camel-yaml-io/src/main/java/org/apache/camel/yaml/io/YamlWriter.java b/core/camel-yaml-io/src/main/java/org/apache/camel/yaml/io/YamlWriter.java
index 2221d6774bc..c7c6e331134 100644
--- a/core/camel-yaml-io/src/main/java/org/apache/camel/yaml/io/YamlWriter.java
+++ b/core/camel-yaml-io/src/main/java/org/apache/camel/yaml/io/YamlWriter.java
@@ -18,184 +18,371 @@ package org.apache.camel.yaml.io;
import java.io.IOException;
import java.io.Writer;
-import java.util.ArrayDeque;
import java.util.ArrayList;
-import java.util.Deque;
+import java.util.Collection;
import java.util.List;
+import java.util.Map;
+import java.util.Stack;
+import java.util.StringJoiner;
+
+import com.fasterxml.jackson.databind.JsonNode;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fasterxml.jackson.dataformat.yaml.YAMLGenerator;
+import com.fasterxml.jackson.dataformat.yaml.YAMLMapper;
+import org.apache.camel.catalog.impl.DefaultRuntimeCamelCatalog;
+import org.apache.camel.tooling.model.BaseOptionModel;
+import org.apache.camel.tooling.model.EipModel;
+import org.apache.camel.util.json.JsonArray;
+import org.apache.camel.util.json.JsonObject;
/**
- * YAML writer which emits nicely formatted documents.
+ * YAML writer which uses Jackson to dump to yaml format.
+ *
+ * Implementation notes:
+ *
+ * This writer is based on the same principle for the XML writer which parses the Camel routes (model classes) and emit
+ * a StAX based events for start/end elements.
+ *
+ * However since the YAML DSL is not as easy to dump as XML, then we need to enrich with additional metadata from the
+ * runtime catalog ({@link EipModel}). We then abuse the {@link EipModel} and store the route details in its metadata.
+ * After this we transform from {@link EipModel} to {@link EipNode} to have a List/Map structure that we then transform
+ * to JSon, and then from JSon to YAML.
+ *
*/
public class YamlWriter {
private final Writer writer;
- private final int spaces;
- private final String lineSeparator;
+ private final DefaultRuntimeCamelCatalog catalog;
+ private final List<EipModel> roots = new ArrayList<>();
+ private boolean routesIsRoot;
+ private final Stack<EipModel> models = new Stack<>();
+ private String expression;
- private final List<EipNode> root = new ArrayList<>();
- private final Deque<EipNode> stack = new ArrayDeque<>();
-
- /**
- * @param writer not null
- */
public YamlWriter(Writer writer) throws IOException {
- this(writer, 2, null);
+ this.writer = writer;
+ this.catalog = new DefaultRuntimeCamelCatalog();
+ this.catalog.setCaching(false); // turn cache off as we store state per node
+ this.catalog.setJSonSchemaResolver(new ModelJSonSchemaResolver());
+ this.catalog.start();
}
- /**
- * @param writer not null
- * @param spaces number of spaces to indent
- * @param lineSeparator could be null, but the normal way is valid line separator ("\n" on UNIX).
- */
- public YamlWriter(Writer writer, int spaces, String lineSeparator) throws IOException {
- this.writer = writer;
- this.spaces = spaces;
- this.lineSeparator = validateLineSeparator(lineSeparator);
+ public void startElement(String name) throws IOException {
+ if ("routes".equals(name)) {
+ routesIsRoot = true;
+ return;
+ }
+
+ EipModel model = catalog.eipModel(name);
+ if (model == null) {
+ // not an EIP model
+ return;
+ }
+
+ EipModel parent = models.isEmpty() ? null : models.peek();
+ model.getMetadata().put("_parent", parent);
+ models.push(model);
+ if (parent == null) {
+ // its a root element
+ roots.add(model);
+ }
+ }
+
+ public void startExpressionElement(String name) throws IOException {
+ // currently building an expression
+ this.expression = name;
+ }
+
+ public void endExpressionElement(String name) throws IOException {
+ // expression complete, back to normal mode
+ this.expression = null;
+ }
+
+ public void endElement(String name) throws IOException {
+ if ("routes".equals(name)) {
+ // we are done
+ writer.write(toYaml());
+ return;
+ }
+
+ EipModel model = catalog.eipModel(name);
+ if (model == null) {
+ // not an EIP model
+ return;
+ }
+
+ EipModel last = models.isEmpty() ? null : models.peek();
+ if (last != null && isLanguage(last)) {
+ if (!models.isEmpty()) {
+ models.pop();
+ }
+ // okay we ended a language which we need to set on a parent EIP
+ EipModel parent = models.isEmpty() ? null : models.peek();
+ if (parent != null) {
+ String key = expressionName(parent, expression);
+ if (key != null) {
+ parent.getMetadata().put(key, last);
+ }
+ }
+ return;
+ }
+
+ if (last != null) {
+ if (!models.isEmpty()) {
+ models.pop();
+ }
+ // is this input/output on the parent
+ EipModel parent = models.isEmpty() ? null : models.peek();
+ if (parent != null) {
+ if ("from".equals(name) && parent.isInput()) {
+ // only set input once
+ parent.getMetadata().put("_input", last);
+ } else if ("choice".equals(parent.getName())) {
+ // special for choice/doCatch/doFinally
+ setMetadata(parent, name, last);
+ } else if (parent.isOutput()) {
+ List<EipModel> list = (List<EipModel>) parent.getMetadata().get("_output");
+ if (list == null) {
+ list = new ArrayList<>();
+ parent.getMetadata().put("_output", list);
+ }
+ list.add(last);
+ } else if ("marshal".equals(parent.getName()) || "unmarshal".equals(parent.getName())) {
+ parent.getMetadata().put("_dataFormatType", last);
+ }
+ }
+ }
+
+ if (models.isEmpty() && !routesIsRoot) {
+ // we are done
+ writer.write(toYaml());
+ }
}
- private static String validateLineSeparator(String lineSeparator) {
- String ls = lineSeparator != null ? lineSeparator : System.lineSeparator();
- if (!(ls.equals("\n") || ls.equals("\r") || ls.equals("\r\n"))) {
- throw new IllegalArgumentException("Requested line separator is invalid.");
+ public void writeText(String name, String text) throws IOException {
+ EipModel last = models.isEmpty() ? null : models.peek();
+ if (last != null) {
+ // special as writeText can be used for list of string values
+ setMetadata(last, name, text);
}
- return ls;
}
- private EipNode getParent() {
- for (EipNode node : stack) {
- if (node.isOutput()) {
- return node;
+ public void writeValue(String value) throws IOException {
+ EipModel last = models.isEmpty() ? null : models.peek();
+ if (last != null) {
+ String key = valueName(last);
+ if (key != null) {
+ last.getMetadata().put(key, value);
}
}
- return null;
}
- public void startElement(String name)
- throws IOException {
+ public void addAttribute(String name, Object value) throws IOException {
+ EipModel last = models.isEmpty() ? null : models.peek();
+ if (last != null) {
+ last.getMetadata().put(name, value);
+ }
+ }
- // public void startElement(String name, boolean supportOutput, boolean supportExpression, boolean language)
- // throws IOException {
- // EipNode parent = getParent();
- // EipNode node = new EipNode(name, parent, supportOutput, supportExpression);
+ private EipNode asExpressionNode(EipModel model, String name) {
+ EipNode node = new EipNode(name, null, false, true);
+ doAsNode(model, node);
+ return node;
+ }
- /*
- if (parent != null && "from".equals(name)) {
- parent.setInput(node);
- } else {
- EipNode last = !stack.isEmpty() ? stack.peek() : null;
- if (last != null && language) {
- last.addExpression(node);
- } else if (last != null && last.isOutput()) {
- last.addOutput(node);
+ private EipNode asNode(EipModel model) {
+ EipNode node = new EipNode(model.getName(), null, false, false);
+ doAsNode(model, node);
+ return node;
+ }
+
+ private void doAsNode(EipModel model, EipNode node) {
+ for (Map.Entry<String, Object> entry : model.getMetadata().entrySet()) {
+ String key = entry.getKey();
+ if ("_input".equals(key)) {
+ EipModel m = (EipModel) entry.getValue();
+ node.setInput(asNode(m));
+ } else if ("_output".equals(key)) {
+ List<EipModel> list = (List) entry.getValue();
+ for (EipModel m : list) {
+ node.addOutput(asNode(m));
+ }
+ } else if ("choice".equals(node.getName()) && "otherwise".equals(key)) {
+ EipModel other = (EipModel) entry.getValue();
+ node.addOutput(asNode(other));
+ } else if ("choice".equals(node.getName()) && "when".equals(key)) {
+ Object v = entry.getValue();
+ if (v instanceof List) {
+ // can be a list in choice
+ List<EipModel> list = (List) v;
+ for (EipModel m : list) {
+ node.addOutput(asNode(m));
+ }
+ } else {
+ node.addOutput(asNode((EipModel) v));
+ }
+ } else if (("marshal".equals(node.getName()) || "unmarshal".equals(node.getName()))
+ && "_dataFormatType".equals(key)) {
+ EipModel other = (EipModel) entry.getValue();
+ node.addOutput(asNode(other));
} else {
- boolean added = addOutput(node, parent);
- if (!added) {
- root.add(node);
+ boolean skip = key.startsWith("_") || key.equals("customId");
+ if (skip) {
+ continue;
+ }
+ String exp = null;
+ if (!isLanguage(model)) {
+ // special for expressions that are a property where we need to use expression name as key
+ exp = expressionName(model, key);
+ }
+ Object v = entry.getValue();
+ if (v instanceof EipModel) {
+ EipModel m = (EipModel) entry.getValue();
+ if (exp == null || "expression".equals(exp)) {
+ v = asExpressionNode(m, m.getName());
+ } else {
+ v = asExpressionNode(m, exp);
+ }
+ }
+ if (exp != null && v instanceof EipNode) {
+ node.addExpression((EipNode) v);
+ } else {
+ node.addProperty(key, v);
+ if ("expression".equals(key)) {
+ node.addProperty("language", model.getName());
+ }
}
}
- // } else if (language) {
- // addExpression(node, parent);
- // } else {
- // boolean added = addOutput(node, parent);
- // if (!added) {
- // root.add(node);
- // }
- }
- stack.push(node);
- */
- }
-
- private void addExpression(EipNode node, EipNode parent) {
- EipNode last = !stack.isEmpty() ? stack.peek() : null;
- if (last != null && last.isExpression()) {
- last.addExpression(node);
- } else if (parent != null) {
- parent.addExpression(node);
- // if (parent.isExpression()) {
- // parent.addExpression(node);
- // } else if (parent.isOutput()) {
- // parent.addOutput(node);
- // } else {
- // addExpression(node, parent.getParent());
- // }
- }
- }
-
- private boolean addOutput(EipNode node, EipNode parent) {
- if (parent == null) {
- return false;
- }
- if (parent.isOutput()) {
- parent.addOutput(node);
- return true;
- } else {
- return addOutput(node, parent.getParent());
}
}
- public void writeText(String text) throws IOException {
- EipNode node = stack.peek();
- if (node != null) {
- node.setText(text);
+ public String toYaml() {
+ JsonArray arr = transformToJson(roots);
+
+ // parse JSON
+ try {
+ JsonNode jsonNodeTree = new ObjectMapper().readTree(arr.toJson());
+ // save it as YAML
+ YAMLMapper mapper = new YAMLMapper();
+ mapper.disable(YAMLGenerator.Feature.WRITE_DOC_START_MARKER);
+ mapper.enable(YAMLGenerator.Feature.MINIMIZE_QUOTES);
+ mapper.enable(YAMLGenerator.Feature.INDENT_ARRAYS_WITH_INDICATOR);
+ String jsonAsYaml = mapper.writeValueAsString(jsonNodeTree);
+ // strip leading yaml indent of 2 spaces (because INDENT_ARRAYS_WITH_INDICATOR is enabled)
+ StringJoiner sj = new StringJoiner("\n");
+ for (String line : jsonAsYaml.split("\n")) {
+ if (line.startsWith(" ")) {
+ line = line.substring(2);
+ }
+ sj.add(line);
+ }
+ sj.add(""); // end with empty line
+ jsonAsYaml = sj.toString();
+ return jsonAsYaml;
+ } catch (Exception e) {
+ throw new RuntimeException(e);
}
}
- public void addAttribute(String key, Object value) throws IOException {
- // skip unwanted IDs
- if ("customId".equals(key)) {
- return;
+ private JsonArray transformToJson(List<EipModel> models) {
+ JsonArray arr = new JsonArray();
+ for (EipModel model : models) {
+ JsonObject jo = asJSonNode(model);
+ arr.add(jo);
}
+ return arr;
+ }
+
+ @SuppressWarnings("unchecked")
+ private JsonObject asJSonNode(EipModel model) {
+ JsonObject answer = new JsonObject();
+ JsonObject jo = new JsonObject();
+ answer.put(model.getName(), jo);
- EipNode node = stack.peek();
- if (node != null) {
- node.addProperty(key, value);
+ for (Map.Entry<String, Object> entry : model.getMetadata().entrySet()) {
+ String key = entry.getKey();
+ boolean skip = key.equals("customId");
+ if (skip) {
+ continue;
+ }
+ Object value = entry.getValue();
+ if (value != null) {
+ if (value instanceof Collection<?>) {
+ Collection<?> col = (Collection<?>) value;
+ List list = new ArrayList<>();
+ for (Object v : col) {
+ Object r = v;
+ if (r instanceof EipModel) {
+ EipNode en = asNode((EipModel) r);
+ value = en.asJsonObject();
+ JsonObject wrap = new JsonObject();
+ wrap.put(en.getName(), value);
+ r = wrap;
+ }
+ list.add(r);
+ }
+ if ("_output".equals(key)) {
+ key = "steps";
+ }
+ // special with "from" where outputs needs to be embedded
+ if (jo.containsKey("from")) {
+ jo = jo.getMap("from");
+ }
+ jo.put(key, list);
+ } else {
+ if (value instanceof EipModel) {
+ EipNode r = asNode((EipModel) value);
+ value = r.asJsonObject();
+ jo.put(r.getName(), value);
+ } else {
+ jo.put(key, value);
+ }
+ }
+ }
}
+
+ return answer;
}
- public void endElement() throws IOException {
- if (!stack.isEmpty()) {
- stack.pop();
- }
- if (stack.isEmpty()) {
- // we are done
- writer.write(toYaml());
+ @SuppressWarnings("unchecked")
+ private static void setMetadata(EipModel model, String name, Object value) {
+ // special for choice
+ boolean array = isArray(model, name);
+ if (array) {
+ List list = (List) model.getMetadata().get(name);
+ if (list == null) {
+ list = new ArrayList<>();
+ model.getMetadata().put(name, list);
+ }
+ list.add(value);
+ } else {
+ model.getMetadata().put(name, value);
}
}
- /**
- * Write a string to the underlying writer
- */
- private void write(String str) throws IOException {
- getWriter().write(str);
+ private static String valueName(EipModel model) {
+ return model.getOptions().stream()
+ .filter(o -> "value".equals(o.getKind()))
+ .map(BaseOptionModel::getName)
+ .findFirst().orElse(null);
}
- /**
- * Get the string used as line separator or LS if not set.
- *
- * @return the line separator
- * @see System#lineSeparator()
- */
- protected String getLineSeparator() {
- return lineSeparator;
+ private static String expressionName(EipModel model, String name) {
+ return model.getOptions().stream()
+ .filter(o -> "expression".equals(o.getKind()))
+ .map(BaseOptionModel::getName)
+ .filter(oName -> name == null || oName.equalsIgnoreCase(name))
+ .findFirst().orElse(null);
}
- /**
- * Get the underlying writer
- *
- * @return the underlying writer
- */
- protected Writer getWriter() {
- return writer;
+ private static boolean isArray(EipModel model, String name) {
+ return model.getOptions().stream()
+ .filter(o -> o.getName().equalsIgnoreCase(name))
+ .map(o -> "array".equals(o.getType()))
+ .findFirst().orElse(false);
}
- public String toYaml() {
- StringBuilder sb = new StringBuilder();
- for (EipNode node : root) {
- String s = node.dump(0, spaces, lineSeparator, true);
- sb.append(s);
- sb.append(lineSeparator);
- }
- return sb.toString();
+ private static boolean isLanguage(EipModel model) {
+ return model.getJavaType().startsWith("org.apache.camel.model.language");
}
}
diff --git a/core/camel-yaml-io/src/main/java/org/apache/camel/yaml/out/BaseWriter.java b/core/camel-yaml-io/src/main/java/org/apache/camel/yaml/out/BaseWriter.java
index 876c600c200..4b751c4bc59 100644
--- a/core/camel-yaml-io/src/main/java/org/apache/camel/yaml/out/BaseWriter.java
+++ b/core/camel-yaml-io/src/main/java/org/apache/camel/yaml/out/BaseWriter.java
@@ -22,14 +22,14 @@ import java.util.List;
import org.w3c.dom.Element;
-import org.apache.camel.yaml.io.NewJacksonYamlWriter;
+import org.apache.camel.yaml.io.YamlWriter;
public class BaseWriter {
- protected final NewJacksonYamlWriter writer;
+ protected final YamlWriter writer;
public BaseWriter(Writer writer, String namespace) throws IOException {
- this.writer = new NewJacksonYamlWriter(writer);
+ this.writer = new YamlWriter(writer);
// namespace is only for XML
}
diff --git a/core/camel-yaml-io/src/test/java/org/apache/camel/yaml/out/ModelWriterTest.java b/core/camel-yaml-io/src/test/java/org/apache/camel/yaml/out/ModelWriterTest.java
index e578168a523..12f21a58386 100644
--- a/core/camel-yaml-io/src/test/java/org/apache/camel/yaml/out/ModelWriterTest.java
+++ b/core/camel-yaml-io/src/test/java/org/apache/camel/yaml/out/ModelWriterTest.java
@@ -28,11 +28,14 @@ import org.apache.camel.model.ChoiceDefinition;
import org.apache.camel.model.ExpressionSubElementDefinition;
import org.apache.camel.model.FromDefinition;
import org.apache.camel.model.LogDefinition;
+import org.apache.camel.model.MarshalDefinition;
import org.apache.camel.model.ModelCamelContext;
import org.apache.camel.model.RouteDefinition;
+import org.apache.camel.model.RoutesDefinition;
import org.apache.camel.model.SetBodyDefinition;
import org.apache.camel.model.SplitDefinition;
import org.apache.camel.model.ToDefinition;
+import org.apache.camel.model.dataformat.CsvDataFormat;
import org.apache.camel.model.language.ConstantExpression;
import org.apache.camel.model.language.HeaderExpression;
import org.apache.camel.model.language.SimpleExpression;
@@ -240,4 +243,57 @@ public class ModelWriterTest {
Assertions.assertEquals(expected, out);
}
+ @Test
+ public void testTwoRoutes() throws Exception {
+ StringWriter sw = new StringWriter();
+ ModelWriter writer = new ModelWriter(sw);
+
+ RoutesDefinition routes = new RoutesDefinition();
+
+ RouteDefinition route = new RouteDefinition();
+ route.setId("myRoute0");
+ route.setInput(new FromDefinition("timer:yaml?period=1000"));
+ SetBodyDefinition sb = new SetBodyDefinition();
+ sb.setExpression(new ConstantExpression("Hello from yaml"));
+ route.addOutput(sb);
+ route.addOutput(new LogDefinition("${body}"));
+ routes.getRoutes().add(route);
+
+ route = new RouteDefinition();
+ route.setId("myRoute1");
+ route.setInput(new FromDefinition("direct:start"));
+ ToDefinition to = new ToDefinition("log:input");
+ route.addOutput(to);
+ ToDefinition to2 = new ToDefinition("mock:result");
+ to2.setPattern("InOut");
+ route.addOutput(to2);
+ routes.getRoutes().add(route);
+
+ writer.writeRoutesDefinition(routes);
+
+ String out = sw.toString();
+ String expected = IOHelper.loadText(new FileInputStream("src/test/resources/route8.yaml"));
+ Assertions.assertEquals(expected, out);
+ }
+
+ @Test
+ public void testMarshal() throws Exception {
+ StringWriter sw = new StringWriter();
+ ModelWriter writer = new ModelWriter(sw);
+
+ RouteDefinition route = new RouteDefinition();
+ route.setId("myRoute9");
+ route.setInput(new FromDefinition("timer:foo"));
+ MarshalDefinition mar = new MarshalDefinition();
+ mar.setDataFormatType(new CsvDataFormat());
+ route.addOutput(mar);
+ route.addOutput(new LogDefinition("${body}"));
+
+ writer.writeRouteDefinition(route);
+
+ String out = sw.toString();
+ String expected = IOHelper.loadText(new FileInputStream("src/test/resources/route9.yaml"));
+ Assertions.assertEquals(expected, out);
+ }
+
}
diff --git a/core/camel-yaml-io/src/test/java/org/apache/camel/yaml/out/XmlToYamlTest.java b/core/camel-yaml-io/src/test/java/org/apache/camel/yaml/out/XmlToYamlTest.java
new file mode 100644
index 00000000000..484fa4e16fb
--- /dev/null
+++ b/core/camel-yaml-io/src/test/java/org/apache/camel/yaml/out/XmlToYamlTest.java
@@ -0,0 +1,78 @@
+/*
+ * 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.yaml.out;
+
+import java.io.FileInputStream;
+import java.io.IOError;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.StringWriter;
+import java.nio.file.Files;
+import java.nio.file.Paths;
+import java.util.stream.Stream;
+
+import org.apache.camel.model.RoutesDefinition;
+import org.apache.camel.xml.in.ModelParser;
+import org.junit.jupiter.api.DisplayName;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.Arguments;
+import org.junit.jupiter.params.provider.MethodSource;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public class XmlToYamlTest {
+
+ public static final String NAMESPACE = "http://camel.apache.org/schema/spring";
+
+ private static final Logger LOG = LoggerFactory.getLogger(XmlToYamlTest.class);
+
+ @ParameterizedTest
+ @MethodSource("routes")
+ @DisplayName("Test xml to yaml for <routes>")
+ void testRoutes(String xml) throws Exception {
+ try (InputStream is = new FileInputStream("../camel-xml-io/src/test/resources/" + xml)) {
+ RoutesDefinition expected = new ModelParser(is, NAMESPACE).parseRoutesDefinition().get();
+ StringWriter sw = new StringWriter();
+ new ModelWriter(sw).writeRoutesDefinition(expected);
+ LOG.info("xml={}\n{}\n", xml, sw);
+ }
+ }
+
+ private static Stream<Arguments> routes() {
+ return definitions("routes");
+ }
+
+ private static Stream<Arguments> definitions(String xml) {
+ try {
+ return Files.list(Paths.get("../camel-xml-io/src/test/resources"))
+ .filter(p -> {
+ try {
+ return Files.isRegularFile(p)
+ && p.getFileName().toString().endsWith(".xml")
+ && Files.readString(p).contains("<" + xml);
+ } catch (IOException e) {
+ throw new IOError(e);
+ }
+ })
+ .map(p -> p.getFileName().toString())
+ .flatMap(p -> Stream.of(Arguments.of(p, NAMESPACE)));
+ } catch (IOException e) {
+ throw new IOError(e);
+ }
+ }
+
+}
diff --git a/core/camel-yaml-io/src/test/resources/route8.yaml b/core/camel-yaml-io/src/test/resources/route8.yaml
new file mode 100644
index 00000000000..cc50bc143c9
--- /dev/null
+++ b/core/camel-yaml-io/src/test/resources/route8.yaml
@@ -0,0 +1,20 @@
+- route:
+ id: myRoute0
+ from:
+ uri: timer:yaml?period=1000
+ steps:
+ - setBody:
+ constant:
+ expression: Hello from yaml
+ - log:
+ message: "${body}"
+- route:
+ id: myRoute1
+ from:
+ uri: direct:start
+ steps:
+ - to:
+ uri: log:input
+ - to:
+ uri: mock:result
+ pattern: InOut
diff --git a/core/camel-yaml-io/src/test/resources/route9.yaml b/core/camel-yaml-io/src/test/resources/route9.yaml
new file mode 100644
index 00000000000..1f02c067d7a
--- /dev/null
+++ b/core/camel-yaml-io/src/test/resources/route9.yaml
@@ -0,0 +1,9 @@
+- route:
+ id: myRoute9
+ from:
+ uri: timer:foo
+ steps:
+ - marshal:
+ csv: {}
+ - log:
+ message: "${body}"