blob: 3a552841d3433fd48776aa79a1c5fe18876a0696 [file] [log] [blame]
/*
* 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.parser;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.List;
import org.w3c.dom.Document;
import org.w3c.dom.Node;
import org.apache.camel.parser.helper.CamelJavaParserHelper;
import org.apache.camel.parser.helper.CamelXmlHelper;
import org.apache.camel.parser.helper.CamelXmlTreeParserHelper;
import org.apache.camel.parser.helper.XmlLineNumberParser;
import org.apache.camel.parser.model.CamelEndpointDetails;
import org.apache.camel.parser.model.CamelNodeDetails;
import org.apache.camel.parser.model.CamelNodeDetailsFactory;
import org.apache.camel.parser.model.CamelRouteDetails;
import org.apache.camel.parser.model.CamelSimpleExpressionDetails;
import org.jboss.forge.roaster.model.util.Strings;
import static org.apache.camel.parser.helper.CamelXmlHelper.getSafeAttribute;
/**
* A Camel XML parser that parses Camel XML routes source code.
* <p/>
* This implementation is higher level details, and uses the lower level parser {@link CamelJavaParserHelper}.
*/
public final class XmlRouteParser {
private XmlRouteParser() {
}
/**
* Parses the XML file and build a route model (tree) of the discovered routes in the XML source file.
*
* @param xml the xml file as input stream
* @param baseDir the base of the source code
* @param fullyQualifiedFileName the fully qualified source code file name
* @return a list of route model (tree) of each discovered route
*/
public static List<CamelNodeDetails> parseXmlRouteTree(InputStream xml, String baseDir, String fullyQualifiedFileName) {
List<CamelNodeDetails> answer = new ArrayList<>();
// try parse it as dom
Document dom = null;
try {
dom = XmlLineNumberParser.parseXml(xml);
} catch (Exception e) {
// ignore as the xml file may not be valid at this point
}
if (dom != null) {
// find any from which is the start of the route
CamelNodeDetailsFactory nodeFactory = CamelNodeDetailsFactory.newInstance();
CamelXmlTreeParserHelper parser = new CamelXmlTreeParserHelper();
List<Node> routes = CamelXmlHelper.findAllRoutes(dom);
for (Node route : routes) {
// parse each route and build
String routeId = getSafeAttribute(route, "id");
String lineNumber = (String) route.getUserData(XmlLineNumberParser.LINE_NUMBER);
String lineNumberEnd = (String) route.getUserData(XmlLineNumberParser.LINE_NUMBER_END);
// we only want the relative dir name from the resource directory, eg META-INF/spring/foo.xml
String fileName = fullyQualifiedFileName;
if (fileName.startsWith(baseDir)) {
fileName = fileName.substring(baseDir.length() + 1);
}
CamelNodeDetails node = nodeFactory.newNode(null, "route");
node.setRouteId(routeId);
node.setFileName(fileName);
node.setLineNumber(lineNumber);
node.setLineNumberEnd(lineNumberEnd);
String column = (String) route.getUserData(XmlLineNumberParser.COLUMN_NUMBER);
if (column != null) {
node.setLinePosition(Integer.valueOf(column));
}
// parse the route and gather all its EIPs
List<CamelNodeDetails> tree = parser.parseCamelRouteTree(route, routeId, node, baseDir, fullyQualifiedFileName);
answer.addAll(tree);
}
}
return answer;
}
/**
* Parses the XML source to discover Camel endpoints.
*
* @param xml the xml file as input stream
* @param baseDir the base of the source code
* @param fullyQualifiedFileName the fully qualified source code file name
* @param endpoints list to add discovered and parsed endpoints
*/
public static void parseXmlRouteEndpoints(InputStream xml, String baseDir, String fullyQualifiedFileName,
List<CamelEndpointDetails> endpoints) throws Exception {
// find all the endpoints (currently only <endpoint> and within <route>)
// try parse it as dom
Document dom = null;
try {
dom = XmlLineNumberParser.parseXml(xml);
} catch (Exception e) {
// ignore as the xml file may not be valid at this point
}
if (dom != null) {
List<Node> nodes = CamelXmlHelper.findAllEndpoints(dom);
for (Node node : nodes) {
String uri = getSafeAttribute(node, "uri");
if (uri != null) {
// trim and remove whitespace noise
uri = trimEndpointUri(uri);
}
if (!Strings.isBlank(uri)) {
String id = getSafeAttribute(node, "id");
String lineNumber = (String) node.getUserData(XmlLineNumberParser.LINE_NUMBER);
String lineNumberEnd = (String) node.getUserData(XmlLineNumberParser.LINE_NUMBER_END);
// we only want the relative dir name from the resource directory, eg META-INF/spring/foo.xml
String fileName = fullyQualifiedFileName;
if (fileName.startsWith(baseDir)) {
fileName = fileName.substring(baseDir.length() + 1);
}
boolean consumerOnly = false;
boolean producerOnly = false;
String nodeName = node.getNodeName();
if ("from".equals(nodeName) || "pollEnrich".equals(nodeName)) {
consumerOnly = true;
} else if ("to".equals(nodeName) || "enrich".equals(nodeName) || "wireTap".equals(nodeName)) {
producerOnly = true;
}
CamelEndpointDetails detail = new CamelEndpointDetails();
detail.setFileName(fileName);
detail.setLineNumber(lineNumber);
detail.setLineNumberEnd(lineNumberEnd);
String column = (String) node.getUserData(XmlLineNumberParser.COLUMN_NUMBER);
if (column != null) {
detail.setLinePosition(Integer.valueOf(column));
}
detail.setEndpointInstance(id);
detail.setEndpointUri(uri);
detail.setEndpointComponentName(endpointComponentName(uri));
detail.setConsumerOnly(consumerOnly);
detail.setProducerOnly(producerOnly);
endpoints.add(detail);
}
}
}
}
/**
* Parses the XML source to discover Camel endpoints.
*
* @param xml the xml file as input stream
* @param baseDir the base of the source code
* @param fullyQualifiedFileName the fully qualified source code file name
* @param simpleExpressions list to add discovered and parsed simple expressions
*/
public static void parseXmlRouteSimpleExpressions(InputStream xml, String baseDir, String fullyQualifiedFileName,
List<CamelSimpleExpressionDetails> simpleExpressions) throws Exception {
// find all the simple expressions
// try parse it as dom
Document dom = null;
try {
dom = XmlLineNumberParser.parseXml(xml);
} catch (Exception e) {
// ignore as the xml file may not be valid at this point
}
if (dom != null) {
List<Node> nodes = CamelXmlHelper.findAllSimpleExpressions(dom);
for (Node node : nodes) {
String simple = node.getTextContent();
String lineNumber = (String) node.getUserData(XmlLineNumberParser.LINE_NUMBER);
String lineNumberEnd = (String) node.getUserData(XmlLineNumberParser.LINE_NUMBER_END);
// we only want the relative dir name from the resource directory, eg META-INF/spring/foo.xml
String fileName = fullyQualifiedFileName;
if (fileName.startsWith(baseDir)) {
fileName = fileName.substring(baseDir.length() + 1);
}
CamelSimpleExpressionDetails detail = new CamelSimpleExpressionDetails();
detail.setFileName(fileName);
detail.setLineNumber(lineNumber);
detail.setLineNumberEnd(lineNumberEnd);
detail.setSimple(simple);
String column = (String) node.getUserData(XmlLineNumberParser.COLUMN_NUMBER);
if (column != null) {
detail.setLinePosition(Integer.valueOf(column));
}
// is it used as predicate or not
boolean asPredicate = isSimplePredicate(node);
detail.setPredicate(asPredicate);
detail.setExpression(!asPredicate);
simpleExpressions.add(detail);
}
}
}
/**
* Parses the XML source to discover Camel routes with id's assigned.
*
* @param xml the xml file as input stream
* @param baseDir the base of the source code
* @param fullyQualifiedFileName the fully qualified source code file name
* @param routes list to add discovered and parsed routes
*/
public static void parseXmlRouteRouteIds(InputStream xml, String baseDir, String fullyQualifiedFileName,
List<CamelRouteDetails> routes) throws Exception {
// find all the endpoints (currently only <route> and within <route>)
// try parse it as dom
Document dom = null;
try {
dom = XmlLineNumberParser.parseXml(xml);
} catch (Exception e) {
// ignore as the xml file may not be valid at this point
}
if (dom != null) {
List<Node> nodes = CamelXmlHelper.findAllRoutes(dom);
for (Node node : nodes) {
String id = getSafeAttribute(node, "id");
String lineNumber = (String) node.getUserData(XmlLineNumberParser.LINE_NUMBER);
String lineNumberEnd = (String) node.getUserData(XmlLineNumberParser.LINE_NUMBER_END);
// we only want the relative dir name from the resource directory, eg META-INF/spring/foo.xml
String fileName = fullyQualifiedFileName;
if (fileName.startsWith(baseDir)) {
fileName = fileName.substring(baseDir.length() + 1);
}
CamelRouteDetails detail = new CamelRouteDetails();
detail.setFileName(fileName);
detail.setLineNumber(lineNumber);
detail.setLineNumberEnd(lineNumberEnd);
detail.setRouteId(id);
routes.add(detail);
}
}
}
/**
* Using simple expressions in the XML DSL may be used in certain places as predicate only
*/
private static boolean isSimplePredicate(Node node) {
// need to check the parent
Node parent = node.getParentNode();
if (parent == null) {
return false;
}
String name = parent.getNodeName();
if (name == null) {
return false;
}
if (name.equals("completionPredicate") || name.equals("completion")) {
return true;
}
if (name.equals("onWhen") || name.equals("when") || name.equals("handled") || name.equals("continued")) {
return true;
}
if (name.equals("retryWhile") || name.equals("filter") || name.equals("validate")) {
return true;
}
// special for loop
if (name.equals("loop")) {
String doWhile = null;
if (parent.getAttributes() != null) {
Node attr = parent.getAttributes().getNamedItem("doWhile");
if (attr != null) {
doWhile = attr.getTextContent();
}
}
if ("true".equalsIgnoreCase(doWhile)) {
return true;
}
}
return false;
}
private static String endpointComponentName(String uri) {
if (uri != null) {
int idx = uri.indexOf(":");
if (idx > 0) {
return uri.substring(0, idx);
}
}
return null;
}
private static String trimEndpointUri(String uri) {
uri = uri.trim();
// if the uri is using new-lines then remove whitespace noise before & and ? separator
uri = uri.replaceAll("(\\s+)(\\&)", "$2");
uri = uri.replaceAll("(\\&)(\\s+)", "$1");
uri = uri.replaceAll("(\\?)(\\s+)", "$1");
return uri;
}
}