Spring Mongo Repository Polymorphism - spring

How do I define Repository interface for polymorphic classes
Ex.
abstract class Source { public String name }
class InternalSource extends Source { public int internalId }
class ExternalSource extends Source { public String contact }
Now I know I cannot define a repository interface like
interface SourceRepo extends Repository<? extends Source, String>{....}
or
interface SourceRepo extends Repository<Source, String> { ....}
Is defining simple plain interface and have an implmentation class is the only way?

Well letting spring to associate mongo document to java class mapping through '_class' attribute would work fine.
Mongo document would like some like this
{_id : "xxx", name : "abc", internalId : 123, _class = "...InternalSource" }
{_id : "xxx", name : "abc", contact: "John doe", _class = "...ExternalSource"}

Related

Override link path for subclasses in Spring Boot

I have a Spring Boot 2.x application backed by MongoDB. I'm trying to add some simple inheritance to my domain model along the lines of:
Parent: Person.java
// JSON annotations to assist in serializing requests to the right class
#JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.EXISTING_PROPERTY, property = "_class")
#JsonSubTypes({
#JsonSubTypes.Type(value = Buyer.class, name = "com.company.domain.Buyer"),
#JsonSubTypes.Type(value = Seller.class, name = "com.company.domain.Seller")
})
#Document(collection = "people")
public abstract class Person {
...
}
Subclass 1: Buyer.java
#Document(collection = "people")
public class Buyer extends Person {
...
}
Subclass 1: Seller.java
#Document(collection = "people")
public class Seller extends Person {
...
}
Essentially I would like Buyers and Sellers to be stored in the same Mongo collection and use the same REST path to operate upon them:
Repository: PeopleRepository.java
#RepositoryRestResource(path = "people", collectionResourceRel = "people")
public interface PeopleRepository extends MongoRepository<Person, String> {
}
This almost works except the HATEOAS links that come back look like:
{
_links: {
self: {
href: http://localhost/services/buyer/5b96c785ba3e18ac91aa8cc9
}
}
}
What I need is for the "buyer" in the href to instead become "people" so that it lines up with the repository endpoint above.
I have tried adding an annotation #ExposesResourceFor(Buyer.class) to the repository which didn't seem to change anything (and I would need another annotation for Seller.class but it's not possible to add two #ExposesResourceFor annotations). I was able to get the links to work by making a second repository just for Sellers:
#RepositoryRestResource(path = "people", collectionResourceRel = "people", export = false)
public interface SellerRepository extends MongoRepository<Seller, String> {
}
...but even with export set to false this seems to interfere with the other repository. There seems to be a 50/50 chance on whether the application will bind the endpoint to the SellerRepository or the PeopleRepository.
Is there anyway to set the resource path for subclasses here?
It looks like I was finally able to get this to work by adding additional repositories (which extend the base repository). It's important not to annotate these repositories.
#RepositoryRestResource(path = "people", collectionResourceRel = "people")
public interface PeopleRepository<T extends Person> extends MongoRepository<T, String> {
...
}
public interface SellerRepository extends PeopleRepository<Seller> { }
public interface BuyerRespository extends PeopleRespository<Buyer> { }

Spring REST repository shows wrong URL

I developed sample application with Spring Boot. I've one abstract class (Employee) and two concrete subclasss for example full time and part time employee.
I preferred a joined type of inheritance and 3 table created by JPA provider.
Also I created REST repository for Employee. Looks like below:
package com.caysever.repository;
import com.caysever.model.Employee;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.rest.core.annotation.RepositoryRestResource;
/**
* Created by alican on 04.05.2017.
*/
#RepositoryRestResource(path = "employee")
public interface EmployeeRepository extends JpaRepository<Employee, Long>{
}
When I invoke the **/employee** URL in browser, I'm getting content as below:
{
"fullTimeEmployees" : [ {
"name" : "Alican",
"surname" : "Akkuş",
"birthDay" : "2017-05-04T12:37:20.189+0000",
"gender" : "MALE",
"totalWorkingHoursOfWeek" : 40,
"_links" : {
"self" : {
"href" : "http://localhost:8080/fullTimeEmployee/1"
},
"fullTimeEmployee" : {
"href" : "http://localhost:8080/fullTimeEmployee/1"
}
}
}
When I invoked this URL for first employee localhost:8080/fullTimeEmployee/1 , I getting the 404 status code, not found. But I will getting the first employee with this URL localhost:8080/employee/1.
You can see all codes at GitHub -> https://github.com/AlicanAkkus/jpa-inheritance-strategy
Why Spring REST generates the fullTimeEmployee URL?
I think that with #RepositoryRestResource you are modifying the export details, such as using /empoyee instead of the default value of /fullTimeEmployee
Try with
#RepositoryRestResource(collectionResourceRel = "fullTimeEmployees", path = "fullTimeEmployees")
Or if you want to use /employee
#RepositoryRestResource(collectionResourceRel = "employee", path = "employee")
The path is sets the segment under which this resource is to be exported and the collectionResourceRel sets the value to use when generating links to the collection resource.
Hope this helps
A workaround for this is to add repository interfaces for the concrete classes, sharing the path of the superclass repository.
#RepositoryRestResource(collectionResourceRel = "employee", path = "employee")
public interface FullTimeEmployeeRepository extends JpaRepository<FullTimeEmployee, Long> {
}
#RepositoryRestResource(collectionResourceRel = "employee", path = "employee")
public interface PartTimeEmployeeRepository extends JpaRepository<PartTimeEmployee, Long> {
}
This will generate the links with the "employee" path regardless of the subclass type.
"_links" : {
"self" : {
"href" : "http://localhost:8080/employee/1"
},
"fullTimeEmployee" : {
"href" : "http://localhost:8080/employee/1"
}
}
I don't know if there is another way to work around the issue.

Spring Data Rest, SpringFox and JpaRepository custom finders

NB: using Spring Boot 1.4.2 + SpringFox 2.6.0
Hi, I'm having an issue with Swagger 2 forms on my API documentation over a #RepositoryRestResource. The code below works fine (REST access OK):
#RepositoryRestResource(collectionResourceRel = "people", path = "people")
public interface PersonRepository extends JpaRepository<Person, Long> {
Person findByLastName(#Param("name") String name);
}
And the HATEOAS links are right too: calling URL /api/people/search
ends up with this (notice parameter "name"):
{
"_links": {
"findByLastName": {
"href": "http://localhost:8080/api/people/search/findByLastName{?name}",
"templated": true
},
"self": {
"href": "http://localhost:8080/api/people/search"
}
}
}
The REST API is ok: URL /api/people/search/findByLastName?name=foobar returns data when executed with a browser
BUT in Swagger the GET parameter type is interpreted as "body" instead of "query" and the form submission (curl ... -d 'foobar'...) fails in 404, attempting to submit "name" as request body.
So I tried to set Swagger explicitly, like this:
#RepositoryRestResource(collectionResourceRel = "people", path = "people")
public interface PersonRepository extends JpaRepository<Person, Long> {
#ApiOperation("Find somebody by it's last name")
#ApiImplicitParams({
#ApiImplicitParam(name = "name", paramType = "query")
})
Person findByLastName(#Param("name") #ApiParam(name = "name") String name);
}
without any success, despite the fact that "name" is well retained in the form as the parameter name in this example :-(
body parameter type on GET query
Does anyone know what could be done to make that Swagger form to work? Thx for your help
This is it : #Param configures Spring Data REST, while #RequestParam fits Swagger
#RepositoryRestResource(collectionResourceRel = "people", path = "people")
public interface PersonRepository extends JpaRepository<Person, Long> {
// #Param Spring Data REST : Use #Param or compile with -parameters on JDK 8
// #RequestParam Swagger : paramType=query cf. $Api*Param
Person findByLastName(#Param("name") #RequestParam("name") String name);
}
Me happy!

Polymorphic (de-)serialization to specific type if field type is an interface with Jackson

I would like to de-/serialize based on an interface and I cannot change the classes because they are 3rd party (org.springframework.data.mongodb.core.geo.GeoJson)
POJO:
public class Geofence {
#Id
private String id;
private String topic;
private Date expiresOn;
private Map<String, String> properties;
private GeoJson geometry;
// getters and setters
}
The geometry field could be GeoJsonPolygon or GeoJsonPoint.
I also added this MixIn class:
#JsonTypeInfo(
use = JsonTypeInfo.Id.NAME,
include = JsonTypeInfo.As.EXISTING_PROPERTY,
property = "type")
#JsonSubTypes({
#JsonSubTypes.Type(value = GeoJsonPolygon.class, name = "Polygon"),
#JsonSubTypes.Type(value = GeoJsonPoint.class, name = "Point")})
public abstract class GeoJsonMixIn implements GeoJson{
#JsonProperty("type")
public String getType() {
return null;
}
#JsonProperty("coordinates")
public Iterable<?> getCoordinates() {
return null;
}
}
If I try to deserialize an object (via a REST interface) like
{
"expiresOn": "2017-01-01",
"topic": "test",
"properties": {
"radius": 200
},
"geometry": {
"type": "Point",
"coordinates": [
13.349997997283936,
52.51448414445241
]
}
}
I get this exception:
Could not read document: Can not construct instance of org.springframework.data.mongodb.core.geo.GeoJsonPoint: no suitable constructor found, can not deserialize from Object value (missing default constructor or creator, or perhaps need to add/enable type information?)
On serialization I get:
Failed to instantiate org.springframework.data.mongodb.core.geo.GeoJson using constructor NO_CONSTRUCTOR with arguments
Is this even possible? How can I achieve this?
Everything works, if I don't use polymorhpism - like if I change it to
private GeoJsonPoint geometry;
and my GeoJson is actually a Point. So there apparently IS a suitable constructor etc.
Probably is too late but I had the same exact problem as you and I figured out that you need to add a configuration on Spring to enable GeoJsonModule. So you need to create a Spring configuration class, if you don't have it already, and add this:
#Configuration
public class SpringMongoDbConfig {
#Bean
public Module registerGeoJsonModule(){
return new GeoJsonModule();
}
}
SpringMongoDbConfig is the class I created for editing Spring Configuration, choose what you prefer.
As you can see from the doc GeoJsonModule it's a specific class for serialization and deserialization. All other code with the Abstract class seems ok to me, it's the way to go.

spring hateoas generates different responses for collection or pojo

I have two classes
import org.springframework.hateoas.ResourceSupport;
public class A{}
public class B{}
public class AResource extends ResourceSupport {
private final A a;
}
public class BResource extends ResourceSupport {
private final B b;
}
#Controller
public class Controller {
#RequestMapping
#ResponseBody
public Set<AResource> giveMeColl() {
}
#RequestMapping
#ResponseBody
public BResource giveMeSingle() {
}
}
both responses add links object but for resource A is "links" and for resource B is "_link" and also structure changes
//RESPONSE FOR A
[
{
"content":{
//my fancy object
},
"links":[
{
"rel": "self",
"href": "http://localhost:8080/myid/22"
}
]
}
]
{
"content":{
//my fancy object
},
"_links":[
{
"self": "http://localhost:8080/myid/22/someelse/33"
}]
}
both resources are constructed with assemblers and both are adding the link from the ids
AResource aresource = new AResource(a);
resource.add(linkTo(methodOn(Controller.class).giveMeColl()).withSelfRel());
BResource bresource = new BResource(b);
resource.add(linkTo(methodOn(Controller.class).giveMeSingle()).withSelfRel());
Response headers for a is
"content-type": "application/json;charset=UTF-8"
and for b
"content-type": "application/hal+json;charset=UTF-8"
Could it be because returning an array is not really Restful? as Some post suggest
p.s. I have added and removed #EnableHypermediaSupport but doesn't seem to affect the problem.
"_links" follows the HAL specification. Spring HATEOAS includes a dedicated serializer for that, but it is only used for classes that extend ResourceSupport.
Returning a simple array is not exactly "unRESTful", but it doesn't meet the REST maturity level 3 (Hypermedia controls). In order to achieve that you can wrap the collection into a Resources instance, which extends ResourceSupport. You should get the same link serialization for both types then.

Resources