I have a simple UserRepository
which exposed using Spring Data REST
.
Here is the User
entity class:
@Document(collection = User.COLLECTION_NAME)
@Setter
@Getter
public class User extends Entity {
public static final String COLLECTION_NAME = "users";
private String name;
private String email;
private String password;
private Set<UserRole> roles = new HashSet<>(0);
}
I've created a UserProjection
class which looks the following way:
@JsonInclude(JsonInclude.Include.NON_NULL)
@Projection(types = User.class)
public interface UserProjection {
String getId();
String getName();
String getEmail();
Set<UserRole> getRoles();
}
Here is the repository class:
@RepositoryRestResource(collectionResourceRel = User.COLLECTION_NAME, path = RestPath.Users.ROOT,
excerptProjection = UserProjection.class)
public interface RestUserRepository extends MongoRepository<User, String> {
// Not exported operations
@RestResource(exported = false)
@Override
<S extends User> S insert(S entity);
@RestResource(exported = false)
@Override
<S extends User> S save(S entity);
@RestResource(exported = false)
@Override
<S extends User> List<S> save(Iterable<S> entites);
}
I've also specified user projection in configuration to make sure it will be used.
config.getProjectionConfiguration().addProjection(UserProjection.class, User.class);
So, when I do GET on /users path, I get the following response (projection is applied):
{
"_embedded" : {
"users" : [ {
"name" : "Yuriy Yunikov",
"id" : "5812193156aee116256a33d4",
"roles" : [ "USER", "ADMIN" ],
"email" : "yyunikov@gmail.com",
"points" : 0,
"_links" : {
"self" : {
"href" : "http://127.0.0.1:8080/users/5812193156aee116256a33d4"
},
"user" : {
"href" : "http://127.0.0.1:8080/users/5812193156aee116256a33d4{?projection}",
"templated" : true
}
}
} ]
},
"_links" : {
"self" : {
"href" : "http://127.0.0.1:8080/users"
},
"profile" : {
"href" : "http://127.0.0.1:8080/profile/users"
}
},
"page" : {
"size" : 20,
"totalElements" : 1,
"totalPages" : 1,
"number" : 0
}
}
However, when I try to make a GET call for single resource, e.g. /users/5812193156aee116256a33d4, I get the following response:
{
"name" : "Yuriy Yunikov",
"email" : "yyunikov@gmail.com",
"password" : "123456",
"roles" : [ "USER", "ADMIN" ],
"_links" : {
"self" : {
"href" : "http://127.0.0.1:8080/users/5812193156aee116256a33d4"
},
"user" : {
"href" : "http://127.0.0.1:8080/users/5812193156aee116256a33d4{?projection}",
"templated" : true
}
}
}
As you may see, the password field is getting returned and projection is not applied. I know there is @JsonIgnore
annotation which can be used to hide sensitive data of resource. However, my User
object is located in different application module which does not know about API or JSON representation, so it does not make sense to mark fields with @JsonIgnore
annotation there.
I've seen a post by @Oliver Gierke here about why excerpt projections are not applied to single resource automatically. However, it's still very inconvenient in my case and I would like to return the same UserProjection
when I get a single resource. Is it somehow possible to do it without creating a custom controller or marking fields with @JsonIgnore
?
I was looking at something similar recently and ended up going round in circles when trying to approach it from the Spring Data /Jackson side of things.
An alternative, and very simple solution, then is to approach it from a different angle and ensure the Projection parameter in the HTTP request is always present. This can be done by using a Servlet Filter to modify the parameters of the incoming request.
This would look something like the below:
I was able to create a
ResourceProcessor
class which applies projections on any resource as suggested in DATAREST-428. It works the following way: if projection parameter is specified in URL - the specified projection will be applied, if not - projection with name default will be returned, applied first found projection will be applied. Also, I had to add customProjectingResource
which ignores the links, otherwise there are two_links
keys in the returning JSON.