I am using open projections with an annotation that sets the type name. Impacts are a recursive interface that adds a property impacts to all objects that have impacts. I only copied relevant/cleaned up code to keep it clear.
@GetMapping("/{id}/impacts")
public ImpactOverview getImpacts(@PathVariable("id") String id) {
return repository.findImpactByLabel(id, Impacts.class);
}
public interface Impacts extends Field,ImpactOverview {
// repo variable is simple spring data repository with a id and projection class params.
@JsonIgnore
@Value("#{@repo.findTable(target.label, T(com.company.view.projections.field.impacts.Table))}")
Table getTable();
default Collection<? extends ImpactOverview> impacts() {
//combine is just making a big collection of collection fields & singular fields
return combine(getTable(), ...);
}
}
public interface Table extends com.company.view.projections.table.Table,ImpactOverview {}
@JsonTypeName(AssetType.FIELD_TYPENAME)
public interface Field {}
@JsonTypeName(AssetType.TABLE_TYPENAME)
public interface Table {}
@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, property = "type")
public interface ImpactOverview {
@JsonInclude(JsonInclude.Include.NON_EMPTY)
@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, property = "type")
default Collection<? extends ImpactOverview> impacts() {
return null;
}
}
Basically this works very well and results in a nested impact overview json structure as follows:
{
"type": "field",
...
"impacts: [
{
"type": "table",
...
},
{
"type": "...",
...
impacts: [...]
}
]
}
Readable as follows: if something changes in this field, we have impact on this table, on this ...(eg. codebase, file...). If we impact eg. the codebase because the field changes, that change might have impact on this program. A recursive nested loop of impacts depending on predefined structure laid out by business.
We have also coded a ControllerAdvice that extends ResponseEntityExcepionHandler.
@ControllerAdvice
@Slf4j
@RequiredArgsConstructor
public class ControllerExceptionHandler extends ResponseEntityExceptionHandler {
@Override
protected ResponseEntity<Object> handleExceptionInternal(final Exception ex,
final Object body,
final HttpHeaders headers,
final HttpStatusCode statusCode,
final WebRequest request) {
return toCompanyException(ex);
}
@ResponseBody
ResponseEntity<Object> toCompanyException(@NonNull Exception e) {
return ResponseEntity.status(Status.of(e)).body(Body.of(e));
}
}
In case this repo.findTable
call fails, a HttpMessageNotWritable
exception is thrown by AbstractJackson2HttpMessageConverter
, we are receiving a json structure that looks like this:
{
"type": "FIELD"
}{
"exception": "http-message-not-writable",
"message": "exception"
}
This happens because the BeanSerializerBase::serializeWithType
method does a writeTypePrefix
in which the "type": "field"
gets written.
Only then the exception happens in the serializeFields
call.
At that moment the field type is already written.
How do I overcome this issue? We only want the exception object to be returned.
If i call all getImpacts up front, all methods will be called/tested, but I will execute all queries twice? Of will there be caching in the proxy of the interface. Or should I store it in some Map<String, Object>
kinda object and have that map be parsed by Jackson?
Found out this is caused by this issue I openend in spring framework
Can be bypassed by removing the extends ResponseEntityExceptionHandler
for us for now.