Jackrabbit XPath Query: UUID with leading number in path - xpath

I have what I think is an interesting problem executing queries in Jackrabbit when a node in the query path is a UUID that start with a number.
For example, this query work fine as the second node starts with a letter, 'f':
/*/JCP/feeadeaf-1dae-427f-bf4e-842b07965a93/label//*[#sequence]
This query however does not, if the first 'f' is replaced with '2':
/*/JCP/2eeadeaf-1dae-427f-bf4e-842b07965a93/label//*[#sequence]
The exception:
Encountered "-" at line 1, column 26.
Was expecting one of:
<IntegerLiteral> ...
<DecimalLiteral> ...
<DoubleLiteral> ...
<StringLiteral> ...
... rest omitted for brevity ...
for statement: for $v in /*/JCP/2eeadeaf-1dae-427f-bf4e-842b07965a93/label//*[#sequence] return $v
My code in general
def queryString = queryFor path
def queryManager = session.workspace.queryManager
def query = queryManager.createQuery queryString, Query.XPATH // fails here
query.execute().nodes
I'm aware my query, with the leading asterisk, may not be the best, but I'm just starting out with querying in general. Maybe using another language other than XPATH might work.
I tried the advice in this post, adding a save before creating the query, but no luck
Jackrabbit Running Queries against UUID
Thanks in advance for any input!

A solution that worked was to try and properly escape parts of the query path, namely the individual steps used to build up the path into the repository. The exception message was somewhat misleading, at least to me, as in made me think that the hyphens were part of the root cause. The root problem was that the leading number in the node name created an illegal XPATH query as suggested above.
A solution in this case is to encode the individual steps into the path and build the rest of the query. Resulting in the leading number only being escaped:
/*/JCP/_x0032_eeadeaf-1dae-427f-bf4e-842b07965a93//*[#sequence]
Code that represents a list of steps or a path into the Jackrabbit repository:
import org.apache.commons.lang3.StringUtils;
import org.apache.jackrabbit.util.ISO9075;
class Path {
List<String> steps; //...
public String asQuery() {
return steps.size() > 0 ? "/*" + asPathString(encodedSteps()) + "//*" : "//*";
}
private String asPathString(List<String> steps) {
return '/' + StringUtils.join(steps, '/');
}
private List<String> encodedSteps() {
List<String> encodedSteps = new ArrayList<>();
for (String step : steps) {
encodedSteps.add(ISO9075.encode(step));
}
return encodedSteps;
}
}
Some more notes:
If we escape more of the query string as in:
/_x002a_/JCP/_x0032_eeadeaf-1dae-427f-bf4e-842b07965a93//_x002a_[#sequence]
Or the original path encoded as a whole as in:
_x002f_a_x002f_fffe4dcf0-360c-11e4-ad80-14feb59d0ab5_x002f_2cbae0dc-35e2-11e4-b5d6-14feb59d0ab5_x002f_c
The queries do not produce the wanted results.
Thanks to #matthias_h and #LarsH

An XML element name cannot start with a digit. See the XML spec's rules for STag, Name, and NameStartChar. Therefore, the "XPath expression"
/*/JCP/2eeadeaf-1dae-427f-bf4e-842b07965a93/label//*[#sequence]
is illegal, because the name test 2eead... isn't a legal XML name.
As such, you can't just use any old UUID as an XML element name nor as a name test in XPath. However if you put a legal NameStartChar on the front (such as _), you can probably use any UUID.
I'm not clear on whether you think you already have XML data with an element named <2eead...> (and are trying to query that element's descendants); if so, whatever tool produced it is broken, as it emits illegal XML. On the other hand if the <2eead...> is something that you yourself are creating, then presumably you have the option of modifying the element name to be a legal XML name.

Related

Quarkus ConfigMapping: how to map properties with keys with and without next level at the same time, like "fields" and "fields.excludes"

Converting my app to use Quarkus #ConfigMapping is not easy, especially when the properties names may be used by other apps and you are not free to change them.
I have two keys like fields(set of string) and fields.excludes(another set of string). I want to know what I can do to map both of them. I tried:
Set<String> fields();
#WithName("fields")
FieldsExcludes fieldsToExclude();
interface FieldsExcludes {
Set<String> excludes();
}
...
No compilation error, but runtime error:
Caused by: java.util.NoSuchElementException: SRCFG00014: The config property app.operator.0000001.endpoint.0.fields.exclude is required but it could not be found in any config source
at io.smallrye.config.SmallRyeConfig.getIndexedValues(SmallRyeConfig.java:114)
at io.smallrye.config.SmallRyeConfig.getValues(SmallRyeConfig.java:106)
...
Of course, I think Quarkus cannot tell if "fields" is a field of set, or a field of type FieldsExcludes. But this kind of configs are valid in YAML.
fields: [a,b,c,d]
fields.exclude: [e]
(if I break line at 2nd line after fields it's not valid; but if I put it like this it's valid, at least for Intellij IDEA)
See here: https://quarkus.io/guides/config-yaml#configuration-key-conflicts, in the doc it's stated that it's valid; I can use a ~ as a null key. So fields as a list and fields.exclude as another object should be supported.
OK at last it seems already supported; the error is due to it's not defined as "Optional<>" so Quarkus can find it in one place but cannot in another. (YAML contains 2 ConfigDetail, one of them does not have excludes, and it has no default value)
The final code is like this:
interface ConfigDetail {
Set<String> fields();
#WithName("fields")
Optional<FieldsToExclude> fieldsToExclude();
}
...
interface FieldsToExclude {
Set<String> exclude();
}
fields:
~: [a,b,c,d]
exclude: [e]
...
fields:
~: [a,b,c,d,e]
...

Freemarker template value missing while first or second letter is capitalized

I am trying to learn how to integrate Spring Boot(2.6.3) with FreeMarker(2.3.31). Everything works fine until I met an FreeMarker error while trying to get the value like ${myObject.pAram!} while the first or second letter of the param is capitalized.
So I did so test work and get the following conclusion:
While param name of an object is first letter or second letter capitalized, FreeMarker couldn`t get the param value. Some code here in case of my poor English:
#Data
public class TestBean {
String param; //no-capitalized param
String aParam; //second letter capitalized param
String Bparam; //first letter capitalized param
String cpRam; //third letter capitalized param
}
Here what I do some setter in controller:
#RequestMapping("/test")
public String insure(#PathVariable String module,HttpServletRequest request) {
TestBean testBean = new TestBean();
testBean.setParam("param-no capitalize");
testBean.setAParam("aParam-capitalize the second letter");
testBean.setBparam("Bparam-capitalize the first letter");
testBean.setCpAram("cpAram-capitalize the third letter");
request.setAttribute("testBean",testBean);
return "test";
}
And what the test.ftl looks like:
<body>
<body>
<div>${testBean.param!"missing param"}</div>
------------------------------
<div>${testBean.aParam!"missing aParam"}</div>
------------------------------
<div>${testBean.Bparam!"missing Bparam"}</div>
------------------------------
<div>${testBean.cpAram!"missing cpAram"}</div>
However, the final html result is:
param-no capitalize
------------------------------
missing aParam
------------------------------
missing Bparam
------------------------------
cpAram-capitalize the third letter
Since I`m new to FreeMarker and Spring Boot, I failed to figure out why.
Could someone tell me the exact reason? Am I missing some point?
Thanks a lot!
The Java Bean property names are deduced from the getter method names, not from the field names.
For Bparam, Lombok will generate getBparam(), which is the same that you will get for a filed named bparam. So the two cases are indistinguishable, and both will give the property name bparam.
For aParam, Lombok will generate getAParam(), which is the same that you will get for a filed named AParam. So the two cases are indistinguishable, and both will give the property name AParam. Now this last is not intuitive (I would rather expect aParam), but these are the rules of Java Beans (not of FreeMarker). Which, in turn is a consequence of the broken camel case conventions of Java (i.e., that you must keep letters that were originally upper case as upper case, instead of only using upper case for the first letter of words, so Java camel case is not reversible in general).

How to get value from a column referenced by a number, from JDBC Response object of Jmeter?

I know they advice to get a cell value this way:
columnValue = vars.getObject("resultObject").get(0).get("Column Name");
as stated on jMeter doc : component reference : JDBC_Request.
But: How to access the same RS cell value by just a number of the column?
RS.get(0).get(4);
...instead of giving it a String of column Name/Label.
edit 1: Lets use Groovy/Java, instead of BeanShell. Thanks.
edit 2: The original motivation was the difference between column Name / Label, as these seem to be not fully guaranteed (? seems to be not clear here, not to me), especially due case-sensitivity ("id"/"ID", "name"/"Name"/"NAME" ..)
It should be something like:
String value = (new ArrayList<String>(vars.getObject("resultObject").get(0).values())).get(4)
More information: Debugging JDBC Sampler Results in JMeter
Be aware that according to HashMap documentation:
This class makes no guarantees as to the order of the map; in particular, it does not guarantee that the order will remain constant over time.
So the order of columns might be a big question mark.
The row itself is a HashMap, defined in source code as:
HashMap<String, Object> row
So using BeanShell syntax, you could get it as
row = vars.getObject("resultObject").get(0); // returns HashMap
In HashMap, you cannot access item (column) by ID. You could, however, apply one of the methods described here, but HashMap doesn't guarantee order, so you cannot be sure what "column 4" will contain.
If you want to be able to loop through all columns, it's better to do it in a Map style, not by index. For example using entrySet() with BeanShell:
for(Map.Entry entry : row.entrySet())
{
log.info(entry.getKey() + "=" + entry.getValue());
}
See various ways to iterate through Map here.

XPath2: A sequence of more than one item is not allowed as the first argument of string()

I have this xml that contains certificates and its attributes. Here is the xml over which I am running the extraction query
<Certificates>
<CertificateAndChain>
<Certificate>
<FriendlyName />
<Thumbprint>EE8B375347FCADDC25547FDDF61866E07D5B6A71</Thumbprint>
<SerialNumber>256BB11DB5BE889E46EBCD85608DA849</SerialNumber>
<Version>3</Version>
<SignatureAlgorithm>sha1RSA</SignatureAlgorithm>
<Issuer>CN=certname</Issuer>
<Subject>CN=certname</Subject>
<NotAfter>2040-09-26T18:30:00Z</NotAfter>
<NotBefore>2016-09-27T18:30:00Z</NotBefore>
<IsVerified>false</IsVerified>
</Certificate>
.
.
.(continued)...
I have written another test over this xml that needs to extract thumbprints of all the certificates that are about to get expired in next 10 days.
As part of the test, , I have written the Extraction query as:
<XPath2ResponseParameter Name="TPName"><ExtractionQuery><![CDATA[string(//CertificateAndChain/Certificate[NotAfter[text() <= (current-dateTime() + xs:dayTimeDuration('P10D'))]]/Thumbprint)]]></ExtractionQuery></XPath2ResponseParameter>
The test is in xml file as well. However, when I run the test, I get this exception:
A sequence of more than one item is not allowed as the first argument of string().
This is probably happening because there are multiple certificate elements present in the xml. So, I couldn't figure out the way to extract all the thumbprints failing the mentioned condition(there could be more than 1 and I need to extract all of them)
Can anyone help me fix this?
You can invoke function in axis when using XPath 2.0. So, try to move string() function to the end of the inner XPath (formatted for readability) :
//CertificateAndChain
/Certificate[
NotAfter[
text() <= (current-dateTime() + xs:dayTimeDuration('P10D'))
]
]/Thumbprint
/string()
UPDATE :
Since CDATA can't receive multiple strings in this context, according to the error message in your first comment, then you need to combine those strings into one, presumably using string-join() :
string-join(
//CertificateAndChain
/Certificate[
NotAfter[
text() <= (current-dateTime() + xs:dayTimeDuration('P10D'))
]
]/Thumbprint
/string()
,' ')

Use Datasource Properties in XPath Expression of SoapUI

I need to know whether it is possible to use a datasource property in XPath Expression panel of XPath Match Configuration. For instance, if we have the following XML document:
<ns1:Ions>
<ns1:Ion>UI</ns1:Ion>
<ns1:IonType>X</ns1:IonType>
<ns1:StartDate>2010-05-10</ns1:StartDate>
</ns1:Ions>
<ns1:Ions>
<ns1:Ion>HH</ns1:Ion>
<ns1:IonType>RI</ns1:IonType>
<ns1:StartDate>1998-11-23</ns1:StartDate>
</ns1:Ions>
<ns1:Ions>
<ns1:Ion>CF</ns1:Ion>
<ns1:IonType>A</ns1:IonType>
<ns1:StartDate>2000-06-10</ns1:StartDate>
</ns1:Ions>
I need to evaluate to see whether a content of IonType is 'A' only if its sibling node, Ion, has a value of 'CF'. I was hoping to accomplish this by setting XPath Match Configuration as following:
XPath Expression (DataSourceInput#ION is 'CF')
declare namespace ns1='http://my.namespace.com';
//ns1:Ions[ns1:Ion[text()=${DataSourceInput#ION}]]/ns1:IonType/text()
Expected Results (DataSourceInput#ION_TYPE is 'A')
${DataSourceInput#ION_TYPE}
Running the test would result in SoapUI [Pro] to error the following, Missing content for xpath declare. If I replace ${DataSourceInput#ION} with an actual value, i.e. 'CF', the test works accordingly (I even tried place single quotes around ${DataSourceInput#ION}, but it didn't work).
Is there another way of accomplish this in SoapUI?
I try what you do and it works for me if I put single quotes around the property:
declare namespace ns1='http://my.namespace.com';
//ns1:Ions[ns1:Ion[text()='${DataSourceInput#ION}']]/ns1:IonType/text()
Did you check that testStep name is exactly DataSourceInput? If there are spaces in the TestStep name (i.e your testStep name is Data Source Input you have to put ${Data Source Input#ION}).
Anyway I give you another way to do so, you can add a testStep of type groovy script after the testStep where you are getting the <Ions>response, and check the assert here like follows:
// get xml holder
def groovyUtils = new com.eviware.soapui.support.GroovyUtils(context);
def ionsHolder = groovyUtils.getXmlHolder("IonsTestStepName#response");
// generate xpath expression
def xpathExpression = "//*:Ions[*:Ion[text()='" + context.expand('${DataSourceInput#ION}') + "']]/*:IonType/text()";
log.info xpathExpression;
// get the node value
def nodeValue = ionsHolder.getNodeValue(xpathExpression);
// check expected value
assert nodeValue == context.expand('${DataSourceInput#ION_TYPE}'),'ERROR IONS VALUE';
Hope this helps,

Resources