Creating a dynamic query with MongoDB, Java and Jongo - java

I'm using a combination of Java, the Play Framework using Java and not Scala, MongoDB and Jongo as my go between for a basic web CRUD app. I keep receiving a JSON parse exception even though my string doesn't contain any illegal characters. It's actually failing on closing curly bracket at the end of the statement. Below is my error and code. The query string is just a string builder, searching if an object is empty or has a value, if it has a value it's appended to a string.
Jongo method:
public static Iterable<OneDomain> findWithQueryString(String queryString){
return domains().find("{#}", queryString).as(OneDomain.class);
}
Controller Methods:
String builder example:
if(queryStringBuilder.toString().equalsIgnoreCase("")){
queryStringBuilder.append("date: {$gte : " + searchObj.dateFrom + ", $lt: " + searchObj.dateTo + "}");
}else{
queryStringBuilder.append(" , ");
queryStringBuilder.append("date: {$gte : " + searchObj.dateFrom + ", $lt: " + searchObj.dateTo + "}");
}
String queryString = queryStringBuilder.toString();
Iterable<OneDomain> filteredIterable = OneDomain.findWithQueryString(queryString);
Gives me this error:
Caused by: com.mongodb.util.JSONParseException:
{"source : Online Lists , blacklist : true , vetted : true , is : false , isNot : true"}
^
with the error on the "}" ending it.
In addition to that, if I try to escape the characters by putting in a \" so it becomes \"date\" it will parse and error out like so:
Caused by: com.mongodb.util.JSONParseException:
{"\"source\" : \"Online Lists\" , \"blacklist\" : true , \"vetted\" : true , \"is\" : false , \"isNot\" : true"}
Can I actually do this or because it's Java being inserted into it, the quotes will be around the whole string and thus it's trying to read it as a single JSON field vs it being the whole query?

First, make sure not to make your self vulnerable to injection attacks. Read up on injection attacks in general, and more specifically on MongoDB, eg OWASP page on Testing for NoSQL injection.
While you can indeed pass a generated query string into the find method I would not advise it. I did the same and had big problem when we generated a query containing the jongo substitution parameter #, ie
// This will throw an exception:
// java.lang.IllegalArgumentException: Not enough parameters passed to query: {"value":"#"}
...find("{" + "\"value\":\"#\"" + "}")
My solution is to pass a DBObject:
import com.mongodb.BasicDBObject
...find("#", new BasicDBObject().append("value", "#"))
It can also be built with the QueryBuilder:
import com.mongodb.QueryBuilder
...find("#", QueryBuilder.start("value").is("#").get())
It would be nice though to have query builder support directly in the Jongo API: https://github.com/bguerout/jongo/issues/173

Found the answer. Need to drop the substitution and instead my method looks like
domains().find("{"+queryString+"}").as(OneDomain.class);

Related

Hibernate QuerySyntaxException with enum when hibernate.query.conventional_java_constants is set to false

Background: Java/Wildfly/Hibernate application with Java 8 and Hibernate 5.3.9.Final.
I have a named query like this
#NamedQuery(
name = DailyStatement.GET_EXPORT_DATES_BY_ORGASTRUCTUREIDS_FOR_MISEXPORT,
query =
" SELECT d.orgaStructureId, MIN(d.workingDay), MAX(d.workingDay) "
+ " FROM DailyStatement d "
+ " WHERE d.orgaStructureId in (:orgaStructureIds) "
+ " AND d.misExportFlag = false "
+ " AND d.state = net.shop.ob.ptm.types.timerecord.EDailyStatmentsState.CLOSED "
+ " AND d.workingDay <= :maxWorkingDay "
+ " GROUP BY d.orgaStructureId "
When starting the application I get an error
DailyStatement.getExportDatesByOrgaStructureIdsForMISExport failed
because of: org.hibernate.hql.internal.ast.QuerySyntaxException:
Invalid path:
'net.shop.ob.ptm.types.timerecord.EDailyStatmentsState.CLOSED' [
SELECT d.orgaStructureId, MIN(d.workingDay), MAX(d.workingDay) FROM
net.shop.ob.ptm.dm.entities.DailyStatement d WHERE d.orgaStructureId
in (:orgaStructureIds) AND d.misExportFlag = false AND d.state
= net.shop.ob.ptm.types.timerecord.EDailyStatmentsState.CLOSED AND d.workingDay <= :maxWorkingDay GROUP BY d.orgaStructureId ]
The referenced, allegedly erroneous enum looks like this
public enum EDailyStatmentsState {
OPEN(0),
ERROR(1),
CLOSED(2);
private int id;
//(...)
The error does not occur if I set the hibernate parameter
hibernate.query.conventional_java_constants
to false
in persistence.xml. In blog post https://vladmihalcea.com/the-performance-penalty-of-class-forname-when-parsing-jpql-and-criteria-queries/ the meaning of this parameter is explained. I would understand the error, if the constant EDailyStatmentsState.CLOSED would not follow standard Java naming conventions, but as far as I can see, it does.
Because of performance reasons I would like to set the Hibernate option
hibernate.query.conventional_java_constants
to false and fix the root cause of the error. What could be causing this?
BTW, This question is not a duplicate of e.g. this question QuerySyntaxException with enum
as the non-standard lower-case enum naming is causing the error there, unlike in my case.
Apparently the problem is that the validation of the classname in Hibrnate is done with the following regex in class org.hibernate.internal.util.ReflectHelper:
[a-z\d]+\.([A-Z]{1}[a-z\d]+)+\$?([A-Z]{1}[a-z\d]+)*\.[A-Z_\$]+
This causes classes with two successive uppercase letters not to validate. This is a known Hibernate bug (HHH-14059) which has been internally fixed in Hibernate by replacing the offending regex with
[a-z\d]+\.([A-Z]+[a-z\d]+)+\$?([A-Z]{1}[a-z\d]+)*\.[A-Z_\$]+
I fixed the issue with a workaround by renaming the class to contain no consecutive uppercase letters.
The issue has also been fixed in Hibernate 5.4.19.

Mongo database Invalid BSON field name exception

I tried to follow this How to use dot in field name?. But it result as the picture. There is a additional space:-
protected Document setNestedField(Document doc, FieldValue parentField, String nestedFieldName, Object value, boolean concatenate) {
if (concatenate) {
doc.put(parentField.getSystemName() + "." + nestedFieldName, value);
}
else {
doc.put(nestedFieldName, value);
}
return doc;
}
Exception:-Invalid BSON field name photographs.inner_fields; nested exception is java.lang.IllegalArgumentException: Invalid BSON field name photographs.inner_fields.
How can I use dot "." in field name. I have to use . as I'm using some 3rd party api and I have no option to replace to something else like [dot]. Please suggest me?
In MongoDB field names cannot contain the dot (.) character as it is part of dot-notation syntax, see the documentation.
What third party API are you using ? Are you sure you need a dot ? Dots are commonly used when parsing JSON and your third party API should not need it.
So, a third party api is both constructing the keys (with periods in them), AND saving that to MongoDB?
I suggest that you open a bug ticker in said API:s tracker.
If this is not the case, encode the periods somewhere in the persistence code - and decode it on the way up.

How to assert a JSON node has child nodes with certain values assigned to them?

I have a JSON String like the following:
json = "{\"Things\": \n" +
" {\"Thing\": {\n" +
" \"ID\":\"123\",\n" +
" \"name\":\"Yet Another Thing\",\n" +
" \"price\":\"$12.99\",\n" }\n" +
" }\n" +
"}";
Is there a way I can assert that the ID of Thing is 123 AND that it's name is "Yet Another Thing" in the same statement/assert?
At the moment, I seem to fail using filters:
JsonPath.read(json, "$.Things.Thing[?(#.ID == '123')].name")
I get the following exception:
java.lang.IllegalArgumentException: Invalid container object
Is that maybe because thereis no array notation [] in the JSON string above? Should there be?
On a related note, is there a good introduction to using Hamcrest (with JSON assert)? I know the official tutorial, but I always seem to get it wrong...
UPDATE: The rational for this was: what if I get several Thing elements back, about whose order I have no guarantee (so I can't match Thing[1] (unless I looped through them all))? How do I make sure one element has both, the right ID and the right name? If I check for the children separately, don't I run the risk that one Thing has the right name and another the right ID, but none has both? (Would that be possible with that JSON format, or would I have to an array in that case anyway, like "Thing": [ { ... }, { ... } ], ... ?
P.S.: I tried to use the JsonPath above as follows in the end: assertEquals("Yet Another Thing", JsonPath.read(json, "$.Things.Thing[?(#.ID == '123')].name"));
So that's where the exception might have come from, too. Also, I initially asked this question on the JsonPath mailing list, but didn't get any replies so far, so was hoping I might get help here quicker... :)
The tutorial gives the solution to your problem, it seems:
JsonAssert.with(json).assertThat("$.Things.Thing.ID", Matchers.equalTo("123"))
.assertThat("$.Things.Thing.name", Matchers.equalTo("Yet Another Thing"));

How to create JSON array in java with non-string key names?

I am using this bootstrap transfer plugin at UI which i have to populate with the help of data sent from java application. This plugin requires the data to be an array of objects with 'value' and 'content' properties.
I can easily create a list in java and convert it to JSON array, but the problem here is that this plugin requires key names as non string names. I tried using string key names but that just didn't worked. I looked up for ways to create non-string key names in JSON and the only way i could find was to write my own parser, and that was also not recommended. So how can i prepare my data in java for this plugin ??
Edit:
As mentioned in the plugin documentation, here is a sample data to populate it.
$(function() {
...
var t = $('#test').bootstrapTransfer();
t.populate([
{value:"1", content:"Apple"},
{value:"2", content:"Orange"},
{value:"3", content:"Banana"},
{value:"4", content:"Peach"},
{value:"5", content:"Grapes"}
]);
...
});
When i prepare a JSON array, it is like {"value":"1", "content":"Apple"} which doesn't work for this plugin.
You might check how it runs again when you use a quoted string. When accessing a JSON object, the quotes for keys are not needed. Take for example this code (in Nodejs):
var console = require("console");
var data = { "value1" : 13,
"value2" : "hello",
value3: 15,
value4 : "hello again" };
console.log("Value 1 = " + data.value1 );
console.log("Value 2 = " + data.value2 );
console.log("Value 3 = " + data.value3 );
console.log("Value 4 = " + data.value4 );
Some of the object is declared with quoted strings, and some are not. All are accessed without the quotes, and my console shows:
Value 1 = 13
Value 2 = hello
Value 3 = 15
Value 4 = hello again
So it really shouldn't matter how your keys are defined in java. I know that isn't an exact answer to your question of how to do it, but you really shouldn't need to.

Unable to parse full javascript if statement from within Java using javascript

So I posted this question
Putting a simple expression language into java
and got a great answer about using ScriptEngine to allow the user to write javascript which I did and it seemed to work
But whilst an expression like
(artist.length>0 ? artist + '-' :'') + (album.length>0 ? album + '-' :'')
works using a full if statement does not
if(artist.length>0) {artist + ':-'} + (album.length>0 ? album + '-' :'')
You might ask why Im doing this, well I was hoping I could use an if:else if:else statement and this was a step towards that
That simply isn't valid javascript. The
<cond> ? <iftrue> : <iffalse>
is the 'expression' form of if-else, and returns the value which can be used.
if {
} else {
}
is the 'statement' version, and is used to execute code, and does NOT return a value.

Categories