Spring Data MongoDB support for inheritance on a nested object - java

So My problem is related to how spring handles inheritance when it comes to fields.
I have four classes
#Document(collection = "A")
public class A {
#Id
id;
#Field
B data;
}
public class B {
Type type
}
public class C extends B {
String cField;
public C() {
super(Type.C);
}
}
public class D extends B {
Integer dField;
public D() {
super(Type.D);
}
}
I'm able to store data into mongodb using this schema and it looks something like this
{
"_id" : ObjectId("5f291f861020d19a3db05123"),
"data" : {
"cField" : "abcd",
"type" : "C",
"_class" : "com.example.C"
},
"_class" : "com.example.A"
}
{
"_id" : ObjectId("5f291f861020d19a3db05124"),
"data" : {
"dField" : NumberInt(30),
"type" : "D",
"_class" : "com.example.D"
},
"_class" : "com.example.A"
}
Now when I try to do a findAll on repository, it does not casts to the child class, but simply returns an instance of B.
What changes do I need to make so that I can get correct child object when getting from db?

Related

MongoDB query with $exists and $elemMatch doesn't work

E.g. I have Java objects:
public class Foo {
private Example example;
}
public class Example {
private String str1;
private String str2;
}
Field example can be null.
I need to get all Foo objects where str1 contains e.g. "text". According to documentation I tried:
#Query(value = "{ 'example' : { $exists : true, $elemMatch : { str1 : { $regex: '.*?0.*'} } } }")
but it returns empty Page.
Define the query in the repository:
#Repository
public interface FooRepo extends MongoRepository<Foo, String> {
#Query("{ 'example' : { $exists : true }, 'example.str1' : { $regex: ?0 } }")
List<Foo> findByExamplePropertyRegex(String regexStr);
}
Sample four documents in foo collection:
{ "example" : { "str1" : "apple", "str2" : "rose" } },
{ "example" : { "str1" : "pineapple", "str2" : "jasmine" } },
{ "other": "stuff" },
{ "example" : null }
Run the query from Spring Boot application using CommandLineRunner:
#Autowired
private FooRepo repo;
// ...
public void run(String... strings) throws Exception {
String regexStr = "apple"; // -or- "in"
List<Foo> list = repo.findByExamplePropertyRegex(regexStr);
list.forEach(System.out::println);
The output will be two documents with the regexStr is "apple", and one document with input "in".
Also, see: $regex operator.

Order of properties in swagger definitions changes from run to run

I use swagger-maven-plugin to generate swagger.json. However, I noticed that an order of properties changes from run to run. For example, it can be:
{
...
"definitions" : {
"MyClass1" : {
"type" : "object",
"properties" : {
"name" : {
"type" : "string"
},
"title" : {
"type" : "string"
},
"description" : {
"type" : "string"
},
}
}
}
...
}
and then after the next generation:
{
...
"definitions" : {
"MyClass1" : {
"type" : "object",
"properties" : {
"description" : {
"type" : "string"
},
"title" : {
"type" : "string"
},
"name" : {
"type" : "string"
}
}
}
}
...
}
My class in Java:
public interface MyClass1 {
String getName();
String getTitle();
String getDescription();
}
It's impossible in Java Runtime to know the exact order of methods declared in a class. If you open java.lang.Class#getDeclaredMethods() (see https://docs.oracle.com/javase/8/docs/api/java/lang/Class.html#getDeclaredMethods--) you will see that The elements in the returned array are not sorted and are not in any particular order..
That's why Jackson can't do it for you.
However, there are 2 solutions:
1.You can use #JsonPropertyOrder annotation:
#JsonPropertyOrder({"name", "title", "description"})
public interface MyClass1 {
String getName();
String getTitle();
String getDescription();
}
2.You can use a class with fields (field order is preserved)
public class MyClass1 {
String name;
String title;
String description;
//Getters skipped
}

How to create embedded predicate using QueryDSL, Spring-Data, MongoDB in Java

My repository implements the following interface:
QueryDslPredicateExecutor<Rule>
The structure of mongo's document(Rule object) is:
{
"_id" : ObjectId("5aa924242a73bec4ce581717"),
"name" : "test-name",
"expressionGroups" : [
{
"type" : "WHEN",
"expressions" : [
{
"name" : "name1",
"values" : ["VAL1", "VAL2", "VAL3"]
},
{
"name" : "name2",
"values" : ["VAL4", "VAL5"]
}
]
},
{
"type" : "FOR",
"expressions" : [
{
"name" : "name3",
"values" : ["VAL6", "VAL7"]
}
]
}
]
}
I want to use the following method to find particular rule within mongodb:
Iterable<T> findAll(Predicate predicate);
I've prepared mongo shell query:
db.rule.find({
'expressionGroups.expressions': {
$all: [
{
'$elemMatch': {
'name': "name1",
'values': "VAL2"
}
},
{
'$elemMatch': {
'name': "name3",
'values': "VAL7"
}
}
]
}
}).pretty()
How can I create com.querydsl.core.types.Predicate based on above query?
There is still no answer and I've met the same issue. So I created such code:
private List<Predicate> toPredicates(String root, Map<String, Object> map) {
List<Predicate> predicates = new ArrayList<>();
for (Map.Entry<String, Object> entry: map.entrySet()) {
String path = root+'.'+entry.getKey();
Object value = entry.getValue();
if (entry.getValue() instanceof Map) {
predicates.addAll(toPredicates(path, (Map<String, Object>) value));
} else {
predicates.add(new SimplePath(path).eq(value.toString()));
}
}
return predicates;
}
private static class SimplePath extends StringPath {
protected SimplePath(String var) {
super(var);
}
}
So, you can parse Json to Map and this is it.

Implementation of Comparable not sorting List

I'm having trouble getting my objects to sort.
Some initial knowledge:
I'm using MongoDB to store my collection, I'm able to retrieve them and get everything back correctly.
I have a class that implements Comparable, with a compareTo function, but I also want to be able to sort on different properties, thus I've added static comparables for each property I want to sort on.
public class PlaceHolder implements Comparable<PlaceHolder>{
private String name;
private String icon;
private String originalLangcode;
//Getters and setters + constructors here, these work 100%.
#Override
public int compareTo(PlaceHolder ph) {
return this.getName().compareTo(ph.getName());
}
public static Comparator<PlaceHolder> nameComparator = new Comparator<PlaceHolder>() {
#Override
public int compare(PlaceHolder ph1, PlaceHolder ph2) {
return ph1.getName().compareTo(ph2.getName());
}
};
public static Comparator<PlaceHolder> iconComparator = new Comparator<PlaceHolder>() {
#Override
public int compare(PlaceHolder ph1, PlaceHolder ph2) {
return ph1.getIcon().compareTo(ph2.getIcon());
}
};
public static Comparator<PlaceHolder> nativeLangComparator = new Comparator<PlaceHolder>() {
#Override
public int compare(PlaceHolder ph1, PlaceHolder ph2) {
return ph1.getNativeLang().compareTo(ph2.getNativeLang());
}
};
}
I've then wrote a function that gets all placeholders from my mongodb, returning them in a list with PlaceHolder objects.
public List<PlaceHolder> getAllPlaceholders(String sortType) {
List<PlaceHolder> list = getPlaceholderList();
switch(sortType) {
case "name":
Collections.sort(list, PlaceHolder.nameComparator);
break;
case "icon":
Collections.sort(list, PlaceHolder.iconComparator);
break;
case "native":
Collections.sort(list, PlaceHolder.nativeLangComparator);
break;
default:
Collections.sort(list, PlaceHolder.nameComparator);
break;
}
return list;
}
I always get my data unsorted:
{ "_id" : { "$oid" : "56653f82972552a4024814a3"} , "name" : "testHolder" , "icon" : "archive" , "originalLangcode" : "ENG"}
{ "_id" : { "$oid" : "5665427a97253f798067c57b"} , "name" : "doItHolder" , "icon" : "car" , "originalLangcode" : "ENG"}
{ "_id" : { "$oid" : "566545dd9725050a53b4a5a8"} , "name" : "testableHolder" , "icon" : "adjust" , "originalLangcode" : "ENG"}
{ "_id" : { "$oid" : "5665479b972511264f55aae1"} , "name" : "dataHolder" , "icon" : "hdd-o" , "originalLangcode" : "ENG"}
I'm failing to see what goes wrong.
I've debugged the comparables, and they seem to work, returning negatives and positives. But the list just doesnt seem to get sorted.
I'm using getAllPlaceholders in my controller, passing it to my Page handler which in turn generates html for a table view.
public class PlaceHolderControllerF extends ControllerAbF {
#Autowired PlaceHolderRepo pr;
#RequestMapping(value = "placeholderlist", method = RequestMethod.GET)
#ResponseBody
public String editLanguage(HttpSession httpSession) {
if (getUser(httpSession) == null) {
return noPermission();
}
return Pages.getPlaceHolderList(pr.getAllPlaceholders("name"));
}
Pages just gets my index html, passes some variables to it that then runs through my templater which fills in the variables into the html.

EclipseLink MOXy JSON Serialization

I have got a sample class:
class Zoo {
public Collection<? extends Animal> animals;
}
When serialized with MOXy, I am getting:
{
"bird": [
{
"name": "bird-1",
"wingSpan": "6 feets",
"preferredFood": "food-1"
}
],
"cat": [
{
"name": "cat-1",
"favoriteToy": "toy-1"
}
],
"dog": [
{
"name": "dog-1",
"breed": "bread-1",
"leashColor": "black"
}
]
}
Why is it using array indicators "[]", while bird, cat, and dog are not arrays?
Second, is there a way to get rid of "bird", "cat", and "dog"?
In other words, I am trying to get to:
{
{
"name": "bird-1",
"wingSpan": "6 feets",
"preferredFood": "food-1"
}
,
{
"name": "cat-1",
"favoriteToy": "toy-1"
}
,
{
"name": "dog-1",
"breed": "bread-1",
"leashColor": "black"
}
}
Thanks,
Behzad
QUESTION #1
Why is it using array indicators "[]", while bird, cat, and dog are
not arrays?
To get this JSON representation you have mapped your model with the #XmlElementRef annotation which tells JAXB to use the value of the #XmlRootElement annotation as the inheritance indicator. With MOXy's JSON binding these become keys. We make the value of these keys JSON arrays since keys are not allowed to repeat.
Zoo
In your model you have the #XmlElementRef annotation on your animals field/property.
import java.util.Collection;
import javax.xml.bind.annotation.XmlElementRef;
class Zoo {
#XmlElementRef
public Collection<? extends Animal> animals;
}
Animal
import javax.xml.bind.annotation.*;
#XmlAccessorType(XmlAccessType.FIELD)
#XmlSeeAlso({Bird.class, Cat.class, Dog.class})
public abstract class Animal {
private String name;
}
Bird
On each of your subclasses you have an #XmlRootElement annotation.
import javax.xml.bind.annotation.XmlRootElement;
#XmlRootElement
public class Bird extends Animal {
private String wingSpan;
private String preferredFood;
}
input.json/Output
{
"bird" : [ {
"name" : "bird-1",
"wingSpan" : "6 feets",
"preferredFood" : "food-1"
} ],
"cat" : [ {
"name" : "cat-1",
"favoriteToy" : "toy-1"
} ],
"dog" : [ {
"name" : "dog-1",
"breed" : "bread-1",
"leashColor" : "black"
} ]
}
For More Information
http://blog.bdoughan.com/2010/11/jaxb-and-inheritance-using-substitution.html
QUESTION #2
Second, is there a way to get rid of "bird", "cat", and "dog"?
You are going to need some sort of inheritance indicator to represent the various subclasses.
OPTION #1 - #XmlDescriminatorNode/#XmlDescriminatorValue
Here I do this using MOXy's #XmlDescriminatorNode/#XmlDescriminatorValue annotations.
Zoo
import java.util.Collection;
class Zoo {
public Collection<? extends Animal> animals;
}
Animal
import javax.xml.bind.annotation.*;
import org.eclipse.persistence.oxm.annotations.XmlDiscriminatorNode;
#XmlAccessorType(XmlAccessType.FIELD)
#XmlSeeAlso({Bird.class, Cat.class, Dog.class})
#XmlDiscriminatorNode("#type")
public abstract class Animal {
private String name;
}
Bird
import org.eclipse.persistence.oxm.annotations.XmlDiscriminatorValue;
#XmlDiscriminatorValue("bird")
public class Bird extends Animal {
private String wingSpan;
private String preferredFood;
}
input.json/Output
{
"animals" : [ {
"type" : "bird",
"name" : "bird-1",
"wingSpan" : "6 feets",
"preferredFood" : "food-1"
}, {
"type" : "cat",
"name" : "cat-1",
"favoriteToy" : "toy-1"
}, {
"type" : "dog",
"name" : "dog-1",
"breed" : "bread-1",
"leashColor" : "black"
} ]
}
For More Information
http://blog.bdoughan.com/2010/11/jaxb-and-inheritance-moxy-extension.html
OPTION #2 - #XmlClassExtractor
ClassExtractor (AnimalExtractor)
You can write some code that will determine the appropriate subclass based on the JSON content.
import org.eclipse.persistence.descriptors.ClassExtractor;
import org.eclipse.persistence.sessions.*;
public class AnimalExtractor extends ClassExtractor {
#Override
public Class extractClassFromRow(Record record, Session session) {
if(null != record.get("#wingSpan") || null != record.get("#preferredFood")) {
return Bird.class;
} else if(null != record.get("#favoriteToy")) {
return Cat.class;
} else {
return Dog.class;
}
}
}
Animal
The #XmlClassExtractor annotation is used to specify the ClassExtractor.
import javax.xml.bind.annotation.*;
import org.eclipse.persistence.oxm.annotations.XmlClassExtractor;
#XmlAccessorType(XmlAccessType.FIELD)
#XmlSeeAlso({Bird.class, Cat.class, Dog.class})
#XmlClassExtractor(AnimalExtractor.class)
public abstract class Animal {
private String name;
}
Bird
Due to how MOXy processes the #XmlElement and #XmlAttribute annotations, any of the data you want to be made available to the ClassExtractor will need to be annotated with #XmlAttribute.
import javax.xml.bind.annotation.XmlAttribute;
public class Bird extends Animal {
#XmlAttribute
private String wingSpan;
#XmlAttribute
private String preferredFood;
}
input.json/Output
{
"animals" : [ {
"wingSpan" : "6 feets",
"preferredFood" : "food-1",
"name" : "bird-1"
}, {
"favoriteToy" : "toy-1",
"name" : "cat-1"
}, {
"breed" : "bread-1",
"leashColor" : "black",
"name" : "dog-1"
} ]
}
For More Information
http://blog.bdoughan.com/2012/02/jaxb-and-inheritance-eclipselink-moxy.html
DEMO CODE
The following demo code can be used with both of the mappings described above.
import java.util.*;
import javax.xml.bind.*;
import javax.xml.transform.stream.StreamSource;
import org.eclipse.persistence.jaxb.JAXBContextProperties;
public class Demo {
public static void main(String[] args) throws Exception {
Map<String, Object> properties = new HashMap<String, Object>();
properties.put(JAXBContextProperties.MEDIA_TYPE, "application/json");
properties.put(JAXBContextProperties.JSON_INCLUDE_ROOT, false);
JAXBContext jc = JAXBContext.newInstance(new Class[] {Zoo.class}, properties);
Unmarshaller unmarshaller = jc.createUnmarshaller();
StreamSource json = new StreamSource("src/forum14210676/input.json");
Zoo zoo = unmarshaller.unmarshal(json, Zoo.class).getValue();
Marshaller marshaller = jc.createMarshaller();
marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);
marshaller.marshal(zoo, System.out);
}
}

Categories

Resources