0

Given a projection for a given class, is there a way to tell Spring to include all default attribute of the class defined in the types of the Projection annotation ?

Given the 2 entity Class

@Entity 
@Data 
public class Client {
    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    private long id;

    private String nom;
    private String adresseLigne1;
    private String adresseLigne2;
    private String ville;

    @ManyToOne
    private Province province; 
    /* Many other attribute */

}

and

@Entity
@Data
public class Province {
    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    private long id;
    private String name;
}

and a Repository for each

@RepositoryRestResource(collectionResourceRel = "Client", path = "Client")
public interface ClientRepository extends PagingAndSortingRepository<Client, Long> {
    List<Client> findBy();
}

-

@RepositoryRestResource(collectionResourceRel = "Province", path = "Province")
public interface ProvinceRepository extends PagingAndSortingRepository<Province, Long> {
    List<Province> findByName(String name);
}

I get the following default json for Client :

{
  "nom" : "Mallowpond High",
  "adresseLigne1" : "895 Gonçal Burg",
  "adresseLigne2" : "Apt. 450",
  "ville" : "Lake Irenehaven",
  "_links" : {
    "self" : {
      "href" : "http://127.0.0.1:8080/api/rest/Client/108"
    },
    "province" : {
      "href" : "http://127.0.0.1:8080/api/rest/Client/108/province"
    }
  }
}

Is there a way to create a projection that would return all attribute of Client without having to write all the getXXX method for all of the attribute in Client

@Projection(name = "inlineProvince", types = { Client.class })
public interface ClientProjection {
    /* A way to tell the projection to include all of Client attribute */
    Province getProvince();  // This is the linked object I want to add to my json output as an in-line map (i.e have the behaviour as if it did not have it's own Repository)
}

so that I can get province embedded in my client JSON when calling http://127.0.0.1:8080/api/rest/Client/108?projection=inline:

{
  "nom" : "Mallowpond High",
  "adresseLigne1" : "895 Gonçal Burg",
  "adresseLigne2" : "Apt. 450",
  "ville" : "Lake Irenehaven",
  "_links" : {
    "self" : {
      "href" : "http://127.0.0.1:8080/api/rest/Client/108"
    },
    "province" : {
      "href" : "http://127.0.0.1:8080/api/rest/Client/108/province"
    }
  },
  "province" : {
    "name" : "Quebec"
  }
}

I found that I can do :

@Projection(name = "inline", types = { Client.class })
public interface ClientProjection {
    @Value("#{target}") 
    Client getClient();
    Province getProvince();  // This is the linked object I want to add to my json output as an in-line map (i.e have the behaviour as if it did not have it's own Repository)
}

but I get both client & province as top element. i.e. province is not in client :

{
  "province" : {
    "name" : "quebec"
  },
  "client" : {
      "nom" : "Mallowpond High",
      "adresseLigne1" : "895 Gonçal Burg",
      "adresseLigne2" : "Apt. 450",
      "ville" : "Lake Irenehaven",
      "_links" : {
        "self" : {
          "href" : "http://127.0.0.1:8080/api/rest/Client/108"
        },
        "province" : {
          "href" : "http://127.0.0.1:8080/api/rest/Client/108/province"
        }
      }
  }
}
chrylis -cautiouslyoptimistic-
  • 75,269
  • 21
  • 115
  • 152
cyberguard
  • 23
  • 4

1 Answers1

0

What your @RestRepositoryResource is producing is called HAL format. What you are asking deviates from the specification of HAL and I would suggest to stay with the standard (for obvious reasons).

In HAL there is support to embed relations into a resource. This would mean you end up with a _embedded field which would then hold a collection of cardinality 1 with the province in there. That would look something like this:

{
  "nom" : "Mallowpond High",
  "adresseLigne1" : "895 Gonçal Burg",
  "adresseLigne2" : "Apt. 450",
  "ville" : "Lake Irenehaven",
  "_links" : {
    "self" : {
      "href" : "http://127.0.0.1:8080/api/rest/Client/108"
    },
    "province" : {
      "href" : "http://127.0.0.1:8080/api/rest/Client/108/province"
    }
  },
  "_embedded"{
     "provinces" : [{
        "name" : "Quebec",
        "_links" : {
          "self" : {
            "href" : "http://127.0.0.1:8080/api/rest/Client/108/province"
          }
        }
     }]
  }
}

Unfortunately this is not easily supported with Spring's @RepositoryRestResource, but still relatively easily achieved. You will need to add a ResourceAssembler and transform the Client into a ClientResource that supports the _embedded field. Look at the this question how you can do this (I use this myself as well)

p.streef
  • 3,652
  • 3
  • 26
  • 50
  • Thanks, that what I was afraid of. I ended up implementing the _embedded on the client side in javascript by following the link. Since different page need different embedded object (some of them can be long), I don't want to write a bunch of projection specific to each page. I was hoping of building a projection where I could control what was present in the embedded portion via extra argument. i.e. ?projection=inline&pays=true&province=true would embed province & pays in addition to the core attribute. – cyberguard Feb 11 '19 at 21:27
  • If you want that much control to avoid over/under fetching maybe graphql can be of use? Or what you can do is use an exerptProjection, but it's not something you can then change dynamically. – p.streef Feb 11 '19 at 21:40
  • Thanks I'll take a look at graphql. I had taken a look at traverson, but while it traverse HAL nicely, I could not find an easy way to "build up" the whole object. It only return the destination and not the data it picked up along the way. My quick read of graphql seem to indicate they do this out of the box. – cyberguard Feb 14 '19 at 14:50