Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Filter DSL and Camel Blueprint support #7

Open
wants to merge 5 commits into
base: master
Choose a base branch
from
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
141 changes: 117 additions & 24 deletions src/xml2dsl/xml2dsl.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,16 @@
from rich import console
from rich.console import Console
import importlib.metadata
import os
import re
import sys

__version__ = importlib.metadata.version('camel-xml2dsl')
ns = {
"camel": "http://camel.apache.org/schema/spring",
"beans": "http://www.springframework.org/schema/beans"
"camelBlueprint": "http://camel.apache.org/schema/blueprint",
"beans": "http://www.springframework.org/schema/beans",
"blueprint": "http://www.osgi.org/xmlns/blueprint/v1.0.0"
}

console = Console()
Expand All @@ -32,6 +35,7 @@ class Converter:

import org.apache.camel.ExchangePattern;
import org.apache.camel.builder.RouteBuilder;
import org.apache.camel.model.dataformat.JaxbDataFormat;
import org.apache.camel.model.dataformat.JsonDataFormat;
import org.apache.camel.model.dataformat.JsonLibrary;
import org.apache.camel.model.rest.RestBindingMode;
Expand Down Expand Up @@ -62,8 +66,7 @@ def xml_to_dsl(self):
description="Transforms xml routes to dsl routes " + __version__)
p.add_argument('--xml', metavar='xml', type=str,
help='xml camel context file', required=True, env_var='XML_CTX_INPUT')
p.add_argument('--beans', metavar='beans', type=str,
help='use beans instead processors', required=False, env_var='USE_BEANS')

args = p.parse_args()
with open(args.xml, "r") as xml_file:
parser = etree.XMLParser(remove_comments=True)
Expand All @@ -88,7 +91,7 @@ def xml_to_dsl(self):
bean_definitions.append(bean)

# Multiline groovy transforms
for idx, node in enumerate(root.findall('.//camel:groovy', ns)):
for idx, node in enumerate(self.find_any_descendent(root, 'groovy')):
code_hash, text = self.preformat_groovy_transformation(node)
transformed = Converter.GROOVY_TEMPLATE \
.replace('>>> index <<<', str(idx)) \
Expand All @@ -100,18 +103,25 @@ def xml_to_dsl(self):
}

# Camel Contexts
for idx, camelContext in enumerate(root.findall('camel:camelContext', ns)):
for idx, camelContext in enumerate(self.find_camel_nodes(root, 'camelContext')):
if 'id' in camelContext.attrib:
console.log("processing camel context", camelContext.attrib['id'])

class_name = camelContext.attrib['id'] if 'id' in camelContext.attrib else f'camelContext{str(idx)}'
class_name = class_name.capitalize()

self.get_namespaces(camelContext)
self.dsl_route += self.analyze_node(camelContext)

# Blueprint Route Contexts
for idx, routeContext in enumerate(self.find_camel_nodes(root, 'routeContext')):
if 'id' in routeContext.attrib:
console.log("processing route context", routeContext.attrib['id'])

self.get_namespaces(routeContext)
self.dsl_route += self.analyze_node(routeContext)

groovy_transformations = '\n\n'.join([v['transformation'] for k, v in self.groovy_transformations.items()])

class_name = os.path.splitext(os.path.basename(args.xml))[0].capitalize()

dsl_route = Converter.CLASS_TEMPLATE \
.replace(">>> groovy transformations <<<", groovy_transformations) \
.replace(">>> beans <<<", ''.join(bean_definitions)) \
Expand All @@ -124,22 +134,35 @@ def xml_to_dsl(self):
def get_namespaces(node):
console.log("namespaces:", node.nsmap)

def analyze_node(self, node):
dslText = ""
def analyze_node(self, node, suffix=''):
dslText = ''
for child in node:
node_name = child.tag.partition('}')[2]

# Skip property placeholders
if node_name == 'propertyPlaceholder':
continue

process_function_name = node_name + "_def"
if suffix == 'dataformat':
process_function_name = f'{node_name}_dataformat_def' if suffix == 'dataformat' else f'{node_name}_def'
suffix = ''
else:
process_function_name = f'{node_name}_def'

console.log("processing node", node_name, child.tag, child.sourceline)
next_node = getattr(self, process_function_name, None)
if next_node is None:
console.log("unknown node", process_function_name, child.sourceline)
sys.exit(1)
dslText += getattr(self, process_function_name)(child)

if suffix:
dslText += getattr(self, process_function_name)(child, suffix)
else:
dslText += getattr(self, process_function_name)(child)

if node_name == 'from':
suffix = ''

return dslText

def analyze_element(self, node):
Expand All @@ -148,16 +171,26 @@ def analyze_element(self, node):
return getattr(self, node_name)(node)

def route_def(self, node):
route_def = self.analyze_node(node)
description_node = self.find_camel_node(node, 'description')
description = ''
if description_node is not None:
self.indentation += 1
description = self.description_def(description_node)
self.indentation -= 1
node.remove(description_node)

route_def = self.analyze_node(node, description)
route_def += self.indent('.end();\n')
self.indentation -= 1
return route_def

def dataFormats_def(self, node):
dataformats = self.analyze_node(node)
dataformats = ''
for child in node:
dataformats += self.analyze_node(child, 'dataformat')
return dataformats

def json_def(self, node):
def json_dataformat_def(self, node):
name = node.attrib['id']
library = f'JsonLibrary.{node.attrib["library"]}' if 'library' in node.attrib else ''

Expand All @@ -173,6 +206,20 @@ def json_def(self, node):

return json_dataformat + '\n'

def jaxb_dataformat_def(self, node):
name = node.attrib['id']
jaxb_dataformat = ''

jaxb_dataformat += self.indent(f'JaxbDataFormat {name} = new JaxbDataFormat();')

if 'contextPath' in node.attrib:
jaxb_dataformat += self.indent(f'{name}.setContextPath("{node.attrib["contextPath"]}");')

if 'encoding' in node.attrib:
jaxb_dataformat += self.indent(f'{name}.encoding("{node.attrib["encoding"]}");')

return jaxb_dataformat + '\n'

def endpoint_def(self, node):
endpoint_id = node.attrib['id']
uri = node.attrib['uri']
Expand Down Expand Up @@ -221,25 +268,26 @@ def redeliveryPolicyProfile_def(self, node):

def onException_def(self, node):
exceptions = []
for exception in node.findall("camel:exception", ns):
for exception in self.find_camel_nodes(node, 'exception'):
exceptions.append(exception.text + ".class")
node.remove(exception)
exceptions = ','.join(exceptions)
onException_def = self.indent('onException(' + exceptions + ')')

indented = False

handled = node.find("camel:handled", ns)
handled = self.find_camel_nodes(node, 'handled')
if handled is not None:
if not indented:
self.indentation += 1
indented = True

onException_def += self.indent('.handled(' + handled[0].text + ')')
handled_expression = self.analyze_node(handled[0])
onException_def += self.indent(f'.handled({handled_expression})')

node.remove(handled)
node.remove(handled[0])

redeliveryPolicy = node.find('camel:redeliveryPolicy', ns)
redeliveryPolicy = self.find_camel_node(node, 'redeliveryPolicy')
if redeliveryPolicy is not None:
if not indented:
self.indentation += 1
Expand Down Expand Up @@ -271,12 +319,14 @@ def onException_def(self, node):
def description_def(self, node):
return self.indent(f'.description("{node.text}")')

def from_def(self, node):
def from_def(self, node, description=''):
routeFrom = self.deprecatedProcessor(node.attrib['uri'])
routeId = node.getparent().attrib['id'] if 'id' in node.getparent().keys() else routeFrom
from_def = self.indent(f'from("{routeFrom}")')
self.indentation += 1
from_def += self.indent(f'.routeId("{routeId}")')
if description:
from_def += description
from_def += self.analyze_node(node)
return from_def

Expand Down Expand Up @@ -442,7 +492,7 @@ def doTry_def(self, node):

def doCatch_def(self, node):
exceptions = []
for exception in node.findall("camel:exception", ns):
for exception in self.find_camel_nodes(node, 'exception'):
exceptions.append(exception.text + ".class")
node.remove(exception)
exceptions = ', '.join(exceptions)
Expand Down Expand Up @@ -623,7 +673,6 @@ def post_def(self, node):

def param_def(self, node):
param = '.param()'
param += '.endParam()'

if 'name' in node.attrib:
param += f'.name("{node.attrib["name"]}")'
Expand All @@ -643,6 +692,8 @@ def param_def(self, node):
if 'dataType' in node.attrib:
param += f'.dataType("{node.attrib["dataType"]}")'

param += '.endParam()'

return self.indent(param)
# param().name("id").type(path).description("The id of the user to get").dataType("int").endParam()

Expand Down Expand Up @@ -672,6 +723,38 @@ def generic_rest_def(self, node, verb):

return rest_call

def filter_def(self, node):
filter_def = self.indent('.filter(' + self.analyze_element(node[0]) + ')' + self.handle_id(node))
node.remove(node[0])
self.indentation += 1
filter_def += self.analyze_node(node)
self.indentation -= 1
filter_def += self.indent(f'.end() // (source line: {str(node.sourceline)})')
return filter_def

def routeContextRef_def(self, node):
return self.indent(f'// TODO: import routeContextRef routes for \"{node.attrib["ref"]}\"')

@staticmethod
def find_camel_node(node, node_name):
found = node.find(f'camel:{node_name}', ns)
if found:
return found[0]

found = node.findall(f'camelBlueprint:{node_name}', ns)
if found:
return found[0]

return None

@staticmethod
def find_camel_nodes(node, node_name):
return node.findall(f'camel:{node_name}', ns) + node.findall(f'camelBlueprint:{node_name}', ns)

@staticmethod
def find_any_descendent(node, node_name):
return node.findall(f'.//camel:{node_name}', ns) + node.findall(f'.//camelBlueprint:{node_name}', ns)

# Text deprecated processor for camel deprecated endpoints and features
@staticmethod
def deprecatedProcessor(text):
Expand All @@ -681,6 +764,9 @@ def deprecatedProcessor(text):
text = re.sub('"', "'", text) # replace all occurrences from " to '
text = re.sub('\n', "", text) # remove all endlines

if text == '${body}':
return text

# convert all property references
for match in re.finditer(r"\$(\{[\w\.\_]+\})", text):
if 'exchangeProperty' not in match.group(0) and 'headers' not in match.group(0):
Expand All @@ -697,11 +783,18 @@ def componentOptions(text):
return text

def set_expression(self, node, set_method, parameter=None):
description_node = self.find_camel_node(node, 'description')
description = ''
if description_node is not None:
description = self.description_def(description_node)
description = description[description.index('.'):]
node.remove(description_node)

predicate = self.analyze_element(node[0])
groovy_predicate = f'.{predicate}' if predicate.startswith("groovy") else ''
predicate = '' if groovy_predicate else predicate
parameter = f'"{parameter.strip()}", ' if parameter else ''
return self.indent(f'.{set_method}({parameter}{predicate.strip()}){groovy_predicate}')
return self.indent(f'.{set_method}({parameter}{predicate.strip()}){description}{groovy_predicate}')

def process_multiline_groovy(self, text):
parts = re.split('\r?\n', text)
Expand Down