1

I have the following projection written for an entity class.

@Projection(name = "instituteProjection", types = { Institute.class })
public interface InstituteProjection {

    String getOrganizationName();

    Address getRegisteredAddress();
}

Now I am trying to apply this projection whenever I call the url http://localhost:8080/institutes/1?projection=instituteProjection which returns a single institute resource. The controller GET method implementation is as follows:

@RequestMapping(value = "institutes/{instituteId}", method = RequestMethod.GET,
        produces = MediaType.APPLICATION_JSON_VALUE)
public ResponseEntity<?> getInstitute(@PathVariable Long instituteId) {
    Institute institute = service.getInstitute(instituteId);
    return new ResponseEntity<>(institute, HttpStatus.OK);
}

The problem is this does not return the institute projection. It returns the default repository json.

The projection only works if I use the SDR generated controller instead of the custom rest controller I have implemented.

So how do I apply the projection in the custom controller?

UPDATE 1 Institute class

@Data
@Entity
public class Institute{

 private String organizationName;

    @OneToOne
    private Address registeredAddress;

    @OneToOne
    private Address mailingAddress;

}

UPDATE 2

Address class

    public class Address {
    @Id
    @GeneratedValue(strategy=GenerationType.IDENTITY)
    @Column(name="id")
    private long addressID;   

    @ManyToOne
    private Street street;

    @ManyToOne
    private Country country;

    private double latitude;

    private double longitude;

    @ManyToOne
    private City city;


}
Charlie
  • 3,113
  • 3
  • 38
  • 60
  • Why are you creating your own Controller when SDR will create the controller for you? – Alan Hay May 01 '17 at 08:14
  • I am following the official tutorial for Building REST services with Spring `https://spring.io/guides/tutorials/bookmarks/`. If I dont use a custom controller I cannot use bean validation properly. – Charlie May 01 '17 at 08:32
  • You can validate using a Validator implementation or using Annotations without any issues in SDR. – Alan Hay May 01 '17 at 08:35
  • How about if I want to define an end point with optional search parameters? – Charlie May 02 '17 at 03:00
  • You could use the built in QueryDSL support which lets you query without defining any query method and by specifying any number of parameters. – Alan Hay May 02 '17 at 08:12
  • Can we discuss this in chat? – Charlie May 02 '17 at 12:37

1 Answers1

3

It's very straightforward. You can use your existing projection, you could even remove the @Projection annotation, this one is not mandatory to get it working with custom controllers.

So the minimum projection would be:

public interface InstituteProjection {

    String getOrganizationName();

    Address getRegisteredAddress();

}

Now, to convert your Institute entity, you need an implementation of the ProjectionFactory interface, an existing one is SpelAwareProxyProjectionFactory.

To make a bean of that type accessible, add a small config:

@Configuration
public class ProjectionFactoryConfig {

    @Bean
    public ProjectionFactory projectionFactory() {
        return new SpelAwareProxyProjectionFactory();
    }

}

And now you can use it in your contoller to convert your Institute to your InstituteProjection:

@Autowired
private ProjectionFactory projectionFactory;

...

@RequestMapping(value = "institutes/{instituteId}", method = RequestMethod.GET, produces = MediaType.APPLICATION_JSON_VALUE)
public ResponseEntity<?> getInstitute(@PathVariable Long instituteId) {
    final Institute institute = service.getInstitute(instituteId);
    final InstituteProjection instituteProjection = projectionFactory.createProjection(InstituteProjection.class, institute);
    return new ResponseEntity<>(instituteProjection, HttpStatus.OK);
}
Kevin Peters
  • 3,314
  • 1
  • 17
  • 38
  • I tried it this way and it gave me this exception `org.springframework.web.util.NestedServletException: Request processing failed; nested exception is java.lang.IllegalStateException: getOutputStream() has already been called for this response` – Charlie Apr 30 '17 at 02:52
  • That should not be related to the Projection handling. It's converted before you return the response and should not be related to any servlet purposes. Did you change something else? Did you try just to comment / deactivate the conversion and returning an Institute again? Just to be sure that this is (not) the cause? if that does not point to the error, can you please post the JPA mapping which is related to Institute and Address. I assume that's an Institute entity with an Address Embeddable, right? – Kevin Peters Apr 30 '17 at 08:39
  • I commented the projection part and it returns the institute json as usual. I am updating the post with Institute entity class. – Charlie Apr 30 '17 at 08:56
  • The Institute mapping seems ok (related to the Address. It seems it's not complete hence an identifier column is missing. Or is there some kind of inheritance?). How about the Address Entity, could you post that? I don't know if it makes sense to post everything here step by step, but it can only be some kind of chained issue. The projection itself should not call getOutputStream() twice in general. Maybe there is something weird. – Kevin Peters Apr 30 '17 at 09:57
  • You're right there is inheritance. The institute class is inhering ID from an abstract class. I have updated the question with Address class. – Charlie Apr 30 '17 at 14:29
  • The inheritance should not be a problem in general and also the mapping of the Address seems ok. I assumed there was some kind of back reference or something, but it looks ok. The 'getOutputStream() has already been called for this response' points to a second attempt to finish the response although the response stream is closed. Maybe there is an Exception which is is raised somewhere and causes this. Is that possible? Did you test / debug the conversion and are you sure there is no exception raise in your controller method? Do you get a valid projection before returning it? – Kevin Peters Apr 30 '17 at 16:29
  • I dont know what I changed but I am able to get the projection. But the projection is not HAL formatted. All the links are missing and IDs are exposed. The thing that bothers me is that if I remove the custom controller and just use the repository endpoint everything works perfectly. I can return the projection if I call the url like this `http://localhost:8080/institute/4?projection=instituteProjection` – Charlie May 01 '17 at 06:18
  • That's nice. I'm glad that it finally works as expected. :) Of course it is no HAL because it's a custom controller, but we should not solve too many problems within this question here. That could help you: http://stackoverflow.com/questions/31758862/enable-hal-serialization-in-spring-boot-for-custom-controller-method – Kevin Peters May 01 '17 at 06:26
  • Let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/143069/discussion-between-delta-charlie-and-kevin-peters). – Charlie May 01 '17 at 06:40