Apache Camel XPath with nodelist - xpath

I would like to concatenate all values from the Apache Camel XPath result and add it to the message context. The header should look like: "|value1|value2|valueN|"
My route looks like:
from("direct:test")
.setHeader("key").xpath("//Identifier", List.class)
.to("mock:result")
What is the best way for doing that? Is there a way to implement an own result type?

As Willem said, you have to write your own processor.
For such a little thing, my favourite way is to declare a function in the class containing the route definition returning an anonymous Processor like this:
private Processor setHeaderWithIdentifiers() {
return exchange -> {
List<String> identifiers = new ArrayList<>();
NodeList nodes = XPathBuilder.xpath("//Identifier").evaluate(exchange, NodeList.class);
for (int i = 0; i < nodes.getLength(); i++) {
identifiers.add(nodes.item(i).getNodeValue());
}
// StringUtils from Apache Commons 3
String idAsString = StringUtils.join(identifiers, "|");
exchange.getIn().setHeader("key", idAsString);
};
}
With that, you do not need to find any complex XPath functions and the code remains clear to understand as long the Processor code remains short.

Related

How do I test my Springboot XML Filtering function

I am fairly new to unit testing and I am trying to filter a XML file in java spring-boot the filtering Function looks like this:
public Document filterRecordsByReleaseDate(Document document, String dateString, RDSymbol symbol) throws ParseException {
Document newDocument = builder.newDocument();
Node root = newDocument.createElement("records");
newDocument.appendChild(root);
Date comparisonDate = new SimpleDateFormat("yyyy-MM-dd").parse(dateString);
NodeList nodeList = document.getElementsByTagName("record");
for (int i = 0; i < nodeList.getLength(); i++) {
Node node = newDocument.adoptNode(nodeList.item(i));
Element element = (Element) node;
String releaseDateString = element.getElementsByTagName("releasedate").item(0).getTextContent();
Date releaseDate = new SimpleDateFormat("yyyy.MM.dd").parse(releaseDateString);
if (releaseDate.after(comparisonDate) && symbol.toString().equals("GT")) {
root.appendChild(node);
} else if (releaseDate.before(comparisonDate) && symbol.toString().equals("LT")) {
root.appendChild(node);
}
}
return newDocument;
}
The function itself is working fine, but I was thinking about how I might unit test this code. Currently, I only have one File which is supplied from the src/main/resourcesfolder. The Data will at some point come from some External Service/DB and it will follow the same format.
In my head I have several Questions:
how do I mock the Input Document for the file
to what should I compare the output of the function?
Concerning question 1: would It be ok to just use the RecordRepository.getXML function as a dependency, as only thing I could do otherwise would be to replicate its code anyways?
Concerning question 2: would It be ok to create a mocks folder in the src/test/resources directory to which I save the outputs of previous succesfull filters, to compare to. I feel like this would make the Test kind of redundant, but I also dont see any other way. Is there something I am not seing?

Castle Core Invocation create a cache key from intercepted method

I'm using a interface interceptor to cache all methods that starts with "Get" but i can't figure out how to generate a unique cache key for every unknown parameter it can be anything and using GetHashCode is not an option as i can't be 100% sure that they have overridden the GetHashCode.
some thoughts was someting in the line of How can I create a unique hashcode for a JObject?
where JSON is used for a JObject i was thinking on JSON serialize every parameter then get the hash code like explained in the link above:
var obj = JToken.Parse(jsonString);
var comparer = new JTokenEqualityComparer();
var hashCode = comparer.GetHashCode(obj);
However i think this will be a performence hit so how can this be solved ?
The code i have so far is this but it wont handle the complex type situation where .ToString won't generate the raw value type like int, string etc.
private string CreateCacheKey(IInvocation invocation)
{
string className = invocation.TargetType.FullName;
string methodName = invocation.Method.Name;
var builder = new StringBuilder(100);
builder.Append(className);
builder.Append(".");
builder.Append(methodName);
for (int i = 0; i < invocation.Arguments.Length; i++)
{
var argument = invocation.Arguments[i];
var argumentValue = invocation.GetArgumentValue(i);
builder.Append("_");
builder.Append(argument);
if (argument != argumentValue)
{
builder.Append(argumentValue);
}
}
return string.Format("{0}-{1}", this.provider.CacheKey, builder);
}
I ended up using GetHashCode as it is the only viable solution if thay dont override the method it will not cache.

Spring SpEL chooses wrong method to invoke

I'm trying to evaluate the following SpEL expression (Spring-expression version 3.1.1):
T(com.google.common.collect.Lists).newArrayList(#iterable)
where #iterable is of type java.lang.Iterable.
Google Guava com.google.common.collect.Lists (version 14.0) does have a method newArrayList(Iterable) but for some reason SpEL chooses to invoke a different method: newArrayList(Object[])
I dived into the code and found the issue to be with org.springframework.expression.spel.support.ReflectiveMethodResolver implementation: it seems to be sensitive to the manner in which methods are sorted by the java.lang.Class::getMethods.
If 2 methods match the invocation (in the case one of the methods is varargs), the later method (in the order) will be invoked, instead of choosing the method that isn't varargs (which is more specific).
It seems like JDK doesn't guarantee the order the methods are sorted: different runs show different order.
Is there a way to overcome this issue?
You can use the collection projections of Spring EL to select all from iterable and convert it to list:
"#iterable.?[true]"
A simple example to test:
Iterable<Integer> it = () -> new Iterator<Integer>() {
private int[] a = new int[]{1, 2, 3};
private int index = 0;
#Override
public boolean hasNext() {
return index < a.length;
}
#Override
public Integer next() {
return a[index++];
}
};
Tmp tmp = new Tmp();
tmp.setO(it);
StandardEvaluationContext context = new StandardEvaluationContext(tmp);
ArrayList<Integer> list = parser.parseExpression("o.?[true]").getValue(context,
ArrayList.class);

Unable to deserialize ArrayOfString back to List<string> in C#

I have seen this question asked, but have not found answers that work so I am asking it again.
I have a restful web service with a POST method that returns a serialized List.
<ArrayOfstring xmlns:i="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://schemas.microsoft.com/2003/10/Serialization/Arrays">
<string>QoL5vA8OKgydWIn%2fdWiu70nobBrJo4N9iXeHmtM7Aj4%3d</string>
<string>vxHyJiSSSvDSZWXOdcfl5FMQC97xxGEWDO8Zy8Qp3X5CwADUbEE8ifACQHR1n7uamEaIUbf85ZuBDB8FFCNY2tJAMJ2jNw09SqGVTpMU5uI06DLtuYnJqsPIF735NOhlvRhBLPnpp62DFMCVsDNHy55UBF3Ggi06ZWTiJ8LTYIf3FYiFLPpXLZ1wWeE5chAWQGfz7zDYJa1OgSZ</string>
<string>OqGAT7Yqe6DfyVD29BeIXFyGtabVCloaC9FA1Fp20JkJ9T17ZzyqhnGxwWda7FqqyJUM8YK9OdcOCgTYrl4JxalECdtJm75TdSG8IAPQlFHp6Gidg4EwZaO9FKahlYMm5JrFpxTmLrdLgMAkYEV7gIR6zIyIByAGwYYDDwH3QCHrym3CuueRnFWAHCJu1LJbx0zRtt7g3yEaTiJ</string>
</ArrayOfstring>
The code performing the call is as follows below.
XDocument xDocResponse = RestPOSTToXDocument(szBaseUri, szInput);
string szNamespace = xDocResponse.Root.Name.Namespace.ToString();
IXmlUtils utility = new XmlUtils();
List<string> lst = utility.DeserializeList<string>(xDocResponse, szNamespace);
I have also attempted a different version of the call as shown below.
List<List<string>> lst = utility.DeserializeList<List<string>>(xDocResponse, szNamespace);
Both versions of the calls generate the error provided below.
An exception of type 'System.InvalidOperationException' occurred in System.Xml.dll but was not handled in user code
Additional information: There is an error in XML document (0, 0).
My Deserialize method is provided below.
public List<T> DeserializeList<T>(XDocument doc, string szNamespace)
{
List<T> result = null;
XmlSerializer serializer = new XmlSerializer(typeof(List<T>), szNamespace);
using (System.Xml.XmlReader reader = doc.CreateReader())
{
result = (List<T>)serializer.Deserialize(reader);
}
return result;
}
The XML within the XDocument is valid, so I do not understand why this error is being generated. Also, even if I attempt to get the elements via navigation in the XDocument, it does not work. If I look at the doc.Root.value, it appears that all of the strings are concatenated together into a single string.
Does anyone have any information on how I may deserialize this XDocument into a List?
Thanks to the assistance of mellamokb, I found the problem. When my List is serialized, it is serialized into an ArrayOfstring. If I take the XML and replace ArrayOfstring with ArrayOfString, the deserialization works.
This does not make much sense. I would welcome an explanation of why this is necessary though it may have something to do with String vs string.
Essentially, I had to do the following:
string szXml = xDocResponse.ToString();
int nEndRoot = szXml.IndexOf(">");
szXml = szXml.Replace("ArrayOfstring", "ArrayOfString");
xDocResponse = utility.LoadXDocument(szXml);
string szNamespace = xDocResponse.Root.Name.Namespace.ToString();
List<string> lst = utility.DeserializeList<string>(xDocResponse, szNamespace);
Simply get the response stream and deserealize into string array and store it to List
Code Snippet:
List<string> listNew=new List<string>();
using (Stream answer = webResponse.GetResponseStream())
{
DataContractSerializer xmlSer = new DataContractSerializer(typeof(string[]));
var stringArr= (string[])xmlSer.ReadObject(answer);
foreach (string item in GenreList)
{
listNew.Add(item);
}
}

concating multiple processing instruction results in a for loop in XQuery,XPath

I need to read all processing instructions with NAME="CONTENTTYPE" and I want to read #VALUE and concatenate all the Values and return in XQuery/XPath.
My XML:
<REG >
<MARKER MRKEID="SLREG:7.1" MRKTYPE="LD DU" MRKDATE="20130909" MRKTIME="10402688"/>
<?METADATA NAME="CONTENTTYPE" VALUE="STATUTE"?>
<?METADATA NAME="CONTENTTYPE" VALUE="LEGISLATIVEDOCUMENT"?>
<?METADATA NAME="CONTENTTYPE" VALUE="PRIMARYSOURCE"?>
<?METADATA NAME="SLTAXTYPE" VALUE="PRIMARYSOURCE"?>
</REG>
ExpectedOutput:
STATUTE
LEGISLATIVEDOCUMENT
PRIMARYSOURCE
Appreciate your help in writing the XQuery/XPath to get the output as above.
Thanks in Advance.
Regards,
Hari
//processing-instruction('METADATA')[matches(., 'NAME="CONTENTTYPE" VALUE="[^"]*"')]/replace(substring-after(., 'VALUE="'), '"', ''). That's XPath 2.0.
Tagging with JDOM helped me find this.
Long answer coming.... XPath does not have the native ability to parse the 'standard' way of adding 'attributes' to ProcessingInstructions. If you want to do the concatenation of the values as part of a single XPath expression I think you are out of luck.... actually, Martin's answer looks promising, but it will return a number of String values, not ProcessingInsructions. JDOM 2.x will need a Filters.string() on the XPath.compile(...) and you will get a List<String> result to path.evaluate(doc).... I think it's simpler to do it outside of the XPath. Especially given that there's only limited support for XPath2.0 by using the Saxon library with JDOM 2.x
As for doing it programmatically, JDOM 2.x helps a fair amount. Taking your example XML I did it two ways, the first way uses a custom Filter on the XPath resultset. The second way does effectively the same thing but restricting the PI's further in the loop.
public static void main(String[] args) throws Exception {
SAXBuilder saxb = new SAXBuilder();
Document doc = saxb.build(new File("data.xml"));
// This custom filter will return PI's that have the NAME="CONTENTTYPE" 'pseudo' attribute...
#SuppressWarnings("serial")
Filter<ProcessingInstruction> contenttypefilter = new AbstractFilter<ProcessingInstruction>() {
#Override
public ProcessingInstruction filter(Object obj) {
// because we know the XPath expression selects Processing Instructions
// we can safely cast here:
ProcessingInstruction pi = (ProcessingInstruction)obj;
if ("CONTENTTYPE".equals(pi.getPseudoAttributeValue("NAME"))) {
return pi;
}
return null;
}
};
XPathExpression<ProcessingInstruction> xp = XPathFactory.instance().compile(
// search for all METADATA PI's.
"//processing-instruction('METADATA')",
// The XPath will return ProcessingInstruction content, which we
// refine with our custom filter.
contenttypefilter);
StringBuilder sb = new StringBuilder();
for (ProcessingInstruction pi : xp.evaluate(doc)) {
sb.append(pi.getPseudoAttributeValue("VALUE")).append("\n");
}
System.out.println(sb);
}
This second way uses the simpler and pre-defined Filters.processingInstruction() but then does the additional filtering manually....
public static void main(String[] args) throws Exception {
SAXBuilder saxb = new SAXBuilder();
Document doc = saxb.build(new File("data.xml"));
XPathExpression<ProcessingInstruction> xp = XPathFactory.instance().compile(
// search for all METADATA PI's.
"//processing-instruction('METADATA')",
// Use the pre-defined filter to set the generic type
Filters.processinginstruction());
StringBuilder sb = new StringBuilder();
for (ProcessingInstruction pi : xp.evaluate(doc)) {
if (!"CONTENTTYPE".equals(pi.getPseudoAttributeValue("NAME"))) {
continue;
}
sb.append(pi.getPseudoAttributeValue("VALUE")).append("\n");
}
System.out.println(sb);
}

Resources