I'm implementing a company internal REST service using spring boot 1.5.2 with Spring Data JPA and Data Rest.
I'm looking for an efficient way to serialize objects as strings when exposing certain domain models using Spring Data Rest-Repositories.
My domain models all extend from BaseEntity
which looks like this:
@MappedSuperclass
@EntityListeners(AuditingEntityListener.class)
public abstract class BaseEntity extends AbstractAuditable<User, Long> implements Serializable {
@Version
private Long version;
}
With this, each domain model has the properties createdBy
, createDate
, lastModifiedBy
and lastModifiedDate
exposed as shown in this example entity:
public class TestEntity extends BaseEntity { private String name; }
The corresponding JSON output looks like this:
{
"createdBy":
{
"name": "testEM",
"contactInfo":
{
"title": null,
"givenName": "GivenName",
"surName": "Surname",
"mail": "[email protected]"
},
"function": "EMPLOYEE",
"department":
{
"name": "mydep"
}
},
"createdDate": "2017-06-12T11:49:17.013Z",
"lastModifiedBy":
{
<same representation as "createdBy">
},
"lastModifiedDate": "2017-06-14T11:27:32.370Z",
"name": "Hello,Name!",
"new": false,
"_links":
{
"self":
{
"href": "http://localhost:8080/testres/1"
},
"testEntity":
{
"href": "http://localhost:8080/testres/1{?projection}",
"templated": true
}
}
}
Now I'd like to achieve a shorter representation of createdBy
and lastModfifiedBy
so that these entries don't contain the User
object. Instead only the name (from User.getName()
) should be displayed:
{
"createdBy": "testEM",
"createdDate": "2017-06-12T11:49:17.013Z",
"lastModifiedBy": "testEM",
"lastModifiedDate": "2017-06-12T11:49:17.013Z",
... // other properties
}
What is the best way to achieve this?
@JsonIdentityInfo
on the User entity -- This one didn't have any effect at all@Bean Jackson2ObjectMapperBuilderCustomizer customizer() {...}
-- Rendered { "createdBy": { "content": "testEM"}}
annotating the overridden method public User getCreatedBy()
in my BaseEntity
class with @JsonSerialize(using= UserJsonSerializer.class)
-- this one throws an exception
{
"timestamp": 1497515751192,
"status": 500,
"error": "Internal Server Error",
"exception": "org.springframework.http.converter.HttpMessageNotWritableException",
"message": "Could not write content: Can not override serializer; nested exception is com.fasterxml.jackson.databind.JsonMappingException: Can not override serializer",
"path": "/testres/1"
}
I've also read about using @JsonView
, however, it didn't become clear to me how to enable these for the given use case
I've created some projections, which is the out-of-the-box supported way to reduce output. See this Gist for the code I've written.
With these in place, and the projections set as excerpts, the list of entries is displayed fine. However, when you request a specific resource like localhost:8080/testRepo/1
you get the unprojected output. I know that Spring won't apply projections to specific entities per default. So we'd have to apply the request parameter ?=projection=testProjection
to each request.
Since this is doable (because the app won't be public) it may be okay, but for others it may not. So the questions still stands, how can we alter the audit info in an efficient way for each resource?
I've read again the Spring Data REST Documentation and stumbled upon this paragraph:
There is another route. If the Address domain object does not have it’s own repository definition, Spring Data REST will inline the data fields right inside the Person resource.
So you have to expose an UserRepository
when the auditor is of type User
.
Coincidently, this is the exact behaviour which I experienced when creating a MWE (minimal working example, can't upload to github, since I'm behind a proxy :( ).
So, with a @RepositoryRestResource UserRepository extends JpaRepository<User, Long>
publicly exposed, Spring generates this JSON:
{
"createdDate": "2017-06-12T11:49:17.013Z",
"lastModifiedDate": "2017-06-14T11:27:32.370Z",
"name": "Hello,EM!",
"new": false,
"_links":
{
"self":
{
"href": "http://localhost:8080/testRepo/1"
},
"testEntity":
{
"href": "http://localhost:8080/testRepo/1{?projection}",
"templated": true
},
"lastModifiedBy":
{
"href": "http://localhost:8080/testRepo/1/lastModifiedBy"
},
"createdBy":
{
"href": "http://localhost:8080/testRepo/1/createdBy"
}
}
}
This behaviour is acceptable for me, so consider this question solved. If anyone has additional input feel free to post!
Any help here is much appreciated!
This isn't a solution for my asked question, but it is an acceptable compromise for me and the company.
Quick solution:
When you expose an RestRepository<User>
in your API and your auditor is of the same type User
, Spring will generate HAL-links to createdBy
and lastModifiedBy
. Both audit dates will be inlined still since they are simple strings (due to the JodaTime conversion).
Example code:
// resolves auditor from SecurityContext
public class AuditorAwareImpl implements AuditorAware<User> {
@Override
public User getCurrentAuditor() {
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
if (authentication != null && authentication.getPrincipal() instanceof WrappedUser) {
WrappedUser principal = (WrappedUser)authentication.getPrincipal();
return principal.getUser();
}
throw new IllegalStateException("No current auditor available!");
}
}
Expose the UserRepository:
//exported is true by default
@RepositoryRestResource(exported = true)
public interface UserRepository extends JpaRepository<User, Long> {
Optional<User> findByName(String loginName);
}
Create AuditEntity from which all other domain objects inherit:
@MappedSuperclass
@EntityListeners(AuditingEntityListener.class)
public abstract class BaseEntity extends AbstractAuditable<User, Long> implements Serializable {
@javax.persistence.Version
private Long version;
}
Expose your domain models:
@Entity
public class Project extends BaseEntity {
private String project_name;
// other properties
}
@RepositoryRestResource
public interface ProjectRepo extends JpaRepository<User, Long> {}
This will generate following JSON for /projects/{id}
:
{
"createdDate": "2017-06-12T11:49:17.013Z",
"lastModifiedDate": "2017-06-14T11:27:32.370Z",
"project_name": "MyExampleProjectName",
"new": false,
"_links":
{
"self":
{
"href": "http://localhost:8080/projects/1"
},
"project":
{
"href": "http://localhost:8080/projects/1{?projection}",
"templated": true
},
"lastModifiedBy":
{
"href": "http://localhost:8080/projects/1/lastModifiedBy"
},
"createdBy":
{
"href": "http://localhost:8080/projects/1/createdBy"
}
}
}