How to augment multiple XML child elements Camel? - apache-camel

I have a use case where I need to take an existing XML document and augment it from a database, for an integration process.
I'm starting with something like:
<parent>
<child>
<data>A</data>
</child>
<child>
<data>B</data>
</child>
<parentData>
<data/>
</parentData>
</parent>
What I am trying to do is add a <moreData .../> tree to each of the child elements.
I could just write a custom bean that does everything, but that doesn't feel the right approach. I've considered using a splitter based on the xpath for child, followed by a content-enricher, which will allow me to fetch the additional data, but I can't see how to reassemble everything afterwards.
At the moment, I'm thinking I need to use a loop, but that feels clunky too, and will require a custom aggregation strategy for the content-enricher.
from("direct:a")
.loop().xpath("count( parent/child )", Integer.class )
.setHeader("Key")
.xpath( "parent/child[function:properties('CamelLoopIndex')]/data", String.class )
.enrich("sql:SELECT xmldata FROM dataTable WHERE key = :#Key?dataSource=myDS",
new MyCustomAggregationStrategy() )
This must be an everyday occurrence in the world of Camel but I can't find any examples of how to do it.
If I were doing this in a custom bean, I'd get an xpath for the child element, then iterate through the nodeset performing the query and attaching the result as a new child to node. I just can't see how to do this "nicely" in Camel.
Any ideas or hints would be great! Thanks!

You can try prepare map of new nodes , and then transform parent xml with xslt and get prepared new nodes using java inside xsl. Here some example. Route:
#Override
public void configure() throws Exception {
from("timer://foo?period=30s")
.setBody(constant("<parent>\n" +
" <child>\n" +
" <data>A</data>\n" +
" </child>\n" +
" <child>\n" +
" <data>B</data>\n" +
" </child>\n" +
" <parentData>\n" +
" <data/>\n" +
" </parentData>\n" +
"</parent>"))
.convertBodyTo(org.w3c.dom.Document.class)
.setProperty("oldBody", simple("body"))
.split(xpath("//child"), (oldExchange, newExchange) -> {
Map<String, String> map = oldExchange != null ? oldExchange.getProperty("map", Map.class) : new HashMap<>();
map.put(newExchange.getIn().getHeader("Key", String.class), newExchange.getIn().getBody(String.class));
newExchange.setProperty("map", map);
return newExchange;
})
.setHeader("Key", xpath("//data/text()"))
// .to("sql:SELECT xmldata FROM dataTable WHERE key = :#Key?dataSource=#myDS")
//emulate result of your sql
.process(exchange -> {
exchange.getIn().setBody("<someNewData>".concat(exchange.getIn().getHeader("Key", String.class).concat("Result")).concat("</someNewData>"));
})
.end()
.setBody(exchangeProperty("oldBody"))
.to("xslt:xslt/result.xsl?transformerFactory=#nsTF")
.log(LoggingLevel.INFO, "Body:${body}");}
public static String getElement(Object map, String key) {
return (String) ((Map) map).get(key);
}
nsTF is bean of class:
public class NonSecureTransfomerFactory extends TransformerFactoryImpl {
#Override
//for using java inside xsl
public boolean isSecureProcessing()
{
return false;
}
}
xslt stylesheet:
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:getter="my.package.RouteHelper">
<xsl:output method="xml" version="1.0" encoding="UTF-8"/>
<xsl:strip-space elements='*'/>
<xsl:param name="map"/>
<xsl:template match="#* | node()">
<xsl:copy>
<xsl:apply-templates select="#* | node()"/>
</xsl:copy>
</xsl:template>
<xsl:template match="child">
<xsl:copy>
<xsl:variable name="key" select="data/text()"/>
<xsl:value-of disable-output-escaping="yes" select="getter:getElement($map,$key)"/>
<xsl:apply-templates select="#* | node()"/>
</xsl:copy>
</xsl:template>
Output xml:
<parent>
<child>
<someNewData>AResult</someNewData>
<data>A</data>
</child>
<child>
<someNewData>BResult</someNewData>
<data>B</data>
</child>
<parentData>
<data/>
</parentData>
</parent>

Related

AvalonEdit - xshd for JSON highlighting

Is there an xshd ruleset for the AvalonEdit control to highlight the JSON syntax? I tried the definition for JavaScript, but it doesn't work well, i.e.:
{
"name" : "value"
}
both name and value have the same color using the JavaScript definition.
Is there a ruleset for JSON, and if not, how can I modify the xshd so that I get different colors for the name and value in JSON?
If somebody needs something like that, I worked it out in following way:
<?xml version="1.0" encoding="utf-8" ?>
<SyntaxDefinition name="Json" extensions=".js" xmlns="http://icsharpcode.net/sharpdevelop/syntaxdefinition/2008">
<Color name="Digits" foreground="#8700FF" exampleText="3.14" />
<Color name="Value" foreground="#000CFF" exampleText="var text = "Hello, World!";" />
<Color name="ParamName" foreground="#057500" exampleText="var text = "Hello, World!";" />
<RuleSet ignoreCase="false">
<Keywords color="Digits" >
<Word>true</Word>
<Word>false</Word>
</Keywords>
<Span color="ParamName">
<Begin>"</Begin>
<End>(?=:)</End>
</Span>
<Span color="Value" multiline="true">
<Begin>
(?<=:)\040"[^"]*
</Begin>
<End>"</End>
</Span>
<Rule color="Digits">\b0[xX][0-9a-fA-F]+|(\b\d+(\.[0-9]+)?|\.[0-9]+)([eE][+-]?[0-9]+)?</Rule>
</RuleSet>
</SyntaxDefinition>
Not perfect, but for me enough.
Just use AvalonEdit's JavaScript highlighter.
Sample code:
using (var stream = Assembly.GetAssembly(typeof(ICSharpCode.AvalonEdit.TextEditor)).GetManifestResourceStream("ICSharpCode.AvalonEdit.Highlighting.Resources.JavaScript-Mode.xshd"))
{
using (var reader = new XmlTextReader(stream))
{
avalonEdit.SyntaxHighlighting = HighlightingLoader.Load(reader, HighlightingManager.Instance);
SearchPanel.Install(avalonEdit);
}
}

How to add attributes all the to XMLElements in Spyne Array

I am using spyne Array to transform a JSON list and I need to add the "id" attribute to the "referral" parent node in the final XML.
This is the final XML I am expecting:
<viewOutboundResponse user="rayners">
<referral id="123">
<status>SUBMITTED</status>
<from>
<outlet id="12345">ABC</outlet>
</from>
<to>
<outlet id="6789">XYZ</outlet>
</to>
<date>2015-01-14</date>
<client>Bloggs</client>
<daysToExpiry>3</daysToExpiry>
</referral>
<referral id="456">
<status>REJECTED</status>
<from>
<outlet id="101112">DEF</outlet>
</from>
<to>
<outlet id="131415">S2X Demo</outlet>
</to>
<date>2004-01-15</date>
<client>Gobbs</client>
<daysToExpiry>7</daysToExpiry>
</referral>
</viewOutboundResponse>
Here is my code:
class ReferralSummaryType(ComplexModel):
__type_name__ = 'referral'
type_info = {'id': XmlAttribute(Integer),
'status': Unicode,
'from': ReferralFromType,
'to': ReferralToType,
'date': Date,
'client': Unicode,
'daysToExpiry': Integer}
class OutboundResponseType(ComplexModel):
__mixin__ = True
referral = Array(ReferralSummaryType)
But the output I am getting is:
<viewOutboundResponse user="rayners">
<referral>
<referral id="123">
<status>SUBMITTED</status>
<from>
<outlet id="12345">ABC</outlet>
</from>
<to>
<outlet id="6789">XYZ</outlet>
</to>
<date>2015-01-14</date>
<client>Bloggs</client>
<daysToExpiry>3</daysToExpiry>
</referral>
<referral id="456">
<status>REJECTED</status>
<from>
<outlet id="101112">DEF</outlet>
</from>
<to>
<outlet id="131415">S2X Demo</outlet>
</to>
<date>2004-01-15</date>
<client>Gobbs</client>
<daysToExpiry>7</daysToExpiry>
</referral>
</referral>
</viewOutboundResponse>
So your question says
I need to add the "id" attribute to the "referral" parent node in the
final XML.
Your desired output has a sequence of referral nodes without a wrapping referral node and the result you are seeing is a sequence of embedded referral nodes (each with the id attribute) but no ID on the wrapping node.
So there is a bit of a conflict there. If it is your need to have an ID in the wrapping referral node, then I think you might need to change your response and add a class for the wrapper type:
class ReferralWrapperType(ComplexModel):
__type_name__ = 'referral'
id = XMLAttribute(Integer)
referral = Array(ReferralSummaryType)
class OutboundResponseType(ComplexModel):
__mixin__ = True
referral = ReferralWrapperType
whereas if what you need is what is shown in what you say is the final XML that you are expecting then from the Spyne array documentation I am led to believe that you could perhaps try:
class OutboundResponseType(ComplexModel):
__mixin__ = True
referral = ReferralSummaryType.customize(max_occurs="unbounded")
Caveat - I'm very very very new to Spyne.
Edited to use max_occurs="unbounded" instead of max_occurs=float('inf') per this spyne bug.
According to the Spyne documentation (http://spyne.io/docs/2.10/manual/03_types.html#arrays), using
referral = ReferralSummaryType.customize(max_occurs="unbounded")
Resolved my issue.
Thanks!

Include SAP Gateway labels in Breeze custom metadata

I am building an Angular 1.3.5 app using Breeze 1.5.1 to interface with a SAP gateway server. One requirement is to minimize duplicate UI translations by re-using the human-readable metadata labels provided by the server. From what I can gather, Breeze's support for custom metadata properties should allow me to load these labels alongside all the standard entity metadata, but I'm struggling to work out exactly how to approach this.
The metadata service provides data in the following format:
<EntityType Name="ContactPersonEmail" sap:content-version="1">
<Key>
<PropertyRef Name="email"/>
</Key>
<Property Name="cpGuid" Type="Edm.Guid" sap:label="Long Text String for XML and HTML Output"/>
<Property Name="primaryEmail" Type="Edm.Boolean" Nullable="false" sap:label="Standard No."/>
<Property Name="email" Type="Edm.String" Nullable="false" MaxLength="241" sap:label="E-Mail Address"/>
<Property Name="homeIndicator" Type="Edm.Boolean" Nullable="false" sap:label="Home address"/>
<Property Name="location" Type="Edm.String" Nullable="false" MaxLength="2" sap:label="Email Type"/>
<Property Name="emailType" Type="Edm.String" Nullable="false" MaxLength="2" sap:label="Email Type"/>
<Property Name="emailTypeText" Type="Edm.String" Nullable="false" MaxLength="60"/>
</EntityType>
where the sap:label attribute is the text I want to add to the Breeze entity type. Is this feasible? The Breeze documentation mostly seems to be focused on loading custom metadata from a local file or already-loaded data, rather than including an additional property on data load, so perhaps this approach is naive.
Once this is resolved I'll work out how to get the text onto the page, but that's for later.
Update: Solution
Per Jeremy's answer below, I was able to run the metadata response through an initialisation function and capture the label field via the metadataProperty's extension property. Example (pre-refactor) code as follows:
// ...
if (entityProperty) {
if (typeof metadataProperty.extensions !== "undefined" && metadataProperty.extensions.length) {
var extension = metadataProperty.extensions[0];
entityProperty[extension.name] = extension.value;
}
}
// ...
The extension.name in this instance is "label", and it's the only extension in the array where it appears. Now all that remains is to get the data onto the UI, but I'm sure that'll be trivial :)
I think this is possible although I'll admit I have only supplemented the Breeze Metadata JSON schema, never XML.
When fetching the metadata on the client you'll just need to do a bit of additional processing to supplement the breeze entity type with your custom metadata properties. In the example code below four custom metadata props are added: displayName, displayOrder, autoGenerateField and allowEmptyStrings.
function initializeMetadataStore(metadataStore, metadata) {
var metadataType, metadataProperty, entityProperty, i, j;
for (i = 0; i < metadata.schema.entityType.length; i++) {
metadataType = metadata.schema.entityType[i];
var entityType = metadataStore.getEntityType(metadataType.name);
for (j = 0; j < metadataType.property.length; j++) {
metadataProperty = metadataType.property[j];
entityProperty = entityType.getProperty(metadataProperty.name);
if (entityProperty) {
if (typeof metadataProperty.displayName !== 'undefined') {
entityProperty.displayName = metadataProperty.displayName;
}
if (typeof metadataProperty.displayOrder !== 'undefined') {
entityProperty.displayOrder = metadataProperty.displayOrder;
}
if (typeof metadataProperty.autoGenerateField !== 'undefined') {
entityProperty.autoGenerateField = metadataProperty.autoGenerateField;
}
if (typeof metadataProperty.allowEmptyStrings !== 'undefined') {
entityProperty.allowEmptyStrings = metadataProperty.allowEmptyStrings;
}
}
}
}
}
var entityManager = ....something...;
entityManager.fetchMetadata(function (metadata) {
return initializeMetadataStore(entityManager.metadataStore, metadata);
});
Here's my answer to a similar question however the OP's backend is .NET and they're using the Breeze Metadata JSON schema.

wix bootstrapper application - install multiple packages on checkboxes

I'm a newbie to Wix burn. I'm making a wix Bootstrapper Application (BA) with multiple msi to install and an UI consisting of one panel with checkboxes and a button install. Each checkbox invites the user to select/unselect a msi, then the user presses "Install" and my BA should install the checked msi.
In my Chain element in the main .wxs file, I plan to use MsiPackage elements with a condition attribute to determine whether yes or no the user has selected it.
Now my question is : how to interface these condition attributes with the checkboxes ? Or in other words: how to get the checkbox information into the Wix .wxs file ?
I can explain how I do it. May there's a better way.
My checkBoxes are bound to properties in a ViewModel class. When a checkBox value is changed in the setter of the property I set the value of a variable
defined in the Bundle.wxs file.
private bool _installApp1Checked;
public bool InstallApp1Checked
{
get { return _installApp1Checked; }
set
{
_installApp1Checked = value;
if (value == true)
{
Bootstrapper.Engine.StringVariables["InstallApp1"] = "1";
}
else
{
Bootstrapper.Engine.StringVariables["InstallApp1"] = string.Empty;
}
RaisePropertyChanged("InstallApp1Checked");
}
}
private bool _installApp2Checked;
public bool InstallApp2Checked
{
get { return InstallApp2Checked; }
set
{
_installApp2Checked = value;
if (value == true)
{
Bootstrapper.Engine.StringVariables["InstallApp2"] = "1";
}
else
{
Bootstrapper.Engine.StringVariables["InstallApp2"] = string.Empty;
}
RaisePropertyChanged("InstallApp2Checked");
}
}
private bool _installApp3Checked;
public bool InstallApp3Checked
{
get { return _installApp3Checked; }
set
{
_installApp3Checked = value;
if (value == true)
{
Bootstrapper.Engine.StringVariables["InstallApp3"] = "1";
}
else
{
Bootstrapper.Engine.StringVariables["InstallApp3"] = string.Empty;
}
RaisePropertyChanged("InstallApp3Checked");
}
}
And in the Bundle.wxs I have:
<Wix ...>
<Bundle ...>
...
<Chain>
...
<MsiPackage>
...
<MsiProperty Name="InstallApp1" Value="[InstallApp1]"/>
<MsiProperty Name="InstallApp2" Value="[InstallApp2]"/>
<MsiProperty Name="InstallApp3" Value="[InstallApp3]"/>
...
</MsiPackage>
</Chain>
</Bundle>
</Wix>
By using the tag the properties of the ViewModel class are available in the wsx file.
Then these values are available at the moment of the installation in my product.wxs:
<Product >
...
<Property Id="InstallApp1">
</Property>
<Property Id="InstallApp2">
</Property>
<Property Id="InstallApp3">
</Property>
<Feature Id="ProductFeature" Title="Alvenos" Level="0">
<ComponentRef Id="ProductComponents" />
<Condition Level="1">InstallApp1</Condition>
</Feature>
<Feature Id="AlvenosVSIXFeature" Title="Alvenos" Level="0">
<ComponentRef Id="AlvenosVsix" />
<Condition Level="1">InstallApp2</Condition>
</Feature>
<Feature Id="AlvenosServerVSIXFeature" Title="Alvenos" Level="0">
<ComponentRef Id="AlvenosServerVsix" />
<Condition Level="1">InstallApp3</Condition>
</Feature>
...
</Product>
You can see the the default value of the Level attribute of the Feature tag is set to 0. That means that the app will not be istalled.
But if in the Condition tag InstallApp[1],[2] or [3] is set 1 the Level is set to 1 and the app is installed.
Use ComponentRef to refernce a Component tag that will contain information about the destination folder of the app that you will install.
<Fragment>
<ComponentGroup Id="InstallApp1" Directory="[target directory id]>
<Component Id="ProductComponent">
<File Source="[your app part of the installer]" />
</Component>
...
</Fragment>
I hope you get the idea.

Displaying a UI List of Strings (Adobe Flex/Actionscript)

Im making an application for a person search. I get the results in the form of a string (each line representing one person). I want to display these lines of text in a List, how do I do this?
<?xml version="1.0" encoding="utf-8"?>
<s:View xmlns:fx="http://ns.adobe.com/mxml/2009"
xmlns:s="library://ns.adobe.com/flex/spark" title="PersonSearchResults">
<fx:Script>
<![CDATA[
import model.PersonSummary;
import mx.collections.ArrayCollection;
public var listOfPeople:Array;
public function populate():void{
trace("Populating");
listOfPeople = String(data).split("\n");
}
]]>
</fx:Script>
<fx:Declarations>
<!-- Place non-visual elements (e.g., services, value objects) here -->
</fx:Declarations>
<s:List id="results">
<s:ArrayList source="{listOfPeople}"/>
</s:List>
The problem I am having is that the listOfPeople array populates AFTER the list has displayed on screen... how do I resolve this?
Thanks
phil
You can't do bindings with an Array. Use ArrayCollection instead.
[Bindable]
public var listOfPeople:ArrayCollection;
public function populate():void{
listOfPeople = new ArrayCollection(String(data).split("\n"));
}
]]>
</fx:Script>
<fx:Declarations>
<!-- Place non-visual elements (e.g., services, value objects) here -->
</fx:Declarations>
<s:List id="results" dataProvider="{listOfPeople}" />

Resources