Skip to content
Original file line number Diff line number Diff line change
Expand Up @@ -21,10 +21,7 @@ public class RestResponseExceptionHandler extends ResponseEntityExceptionHandler
private static final Logger log = LoggerFactory.getLogger(RestResponseExceptionHandler.class);

@Override
protected ResponseEntity<Object> handleHttpMessageNotWritable(HttpMessageNotWritableException exception,
HttpHeaders headers,
HttpStatusCode status,
WebRequest request) {
protected ResponseEntity<Object> handleHttpMessageNotWritable(HttpMessageNotWritableException exception, HttpHeaders headers, HttpStatusCode status, WebRequest request) {
try {
Throwable cause = exception.getCause();
if (!(cause instanceof JsonMappingException jme)) {
Expand All @@ -33,25 +30,85 @@ protected ResponseEntity<Object> handleHttpMessageNotWritable(HttpMessageNotWrit
}

List<JsonMappingException.Reference> path = jme.getPath();

if (log.isTraceEnabled()) {
log.trace("JSON write failed (cause). msg={} | pathRef={}",
jme.getOriginalMessage(),
jme.getPathReference(),
jme);

tracePathAll(path);
}

if (log.isDebugEnabled()) {
for (int i = 0; i < path.size(); i++) {
log.debug("Reference[{}]: {}", i, path.get(i));
}
}

if (path.size() > 3) {
Object fieldFrom = path.getLast().getFrom();
log.debug("Field of class [{}] from: {}", fieldFrom == null ? "null" : fieldFrom.getClass(), fieldFrom);
Object caseFrom = path.get(path.size() - 3).getFrom();
log.debug("Case of class [{}] from: {}", caseFrom == null ? "null" : caseFrom.getClass(), caseFrom);

if (fieldFrom instanceof Field field && caseFrom instanceof Case useCase) {
log.debug("[{}] Could not parse value of field [{}], value [{}] | path={}",
useCase.getStringId(), field.getStringId(), field.getValue(), jme.getPathReference());
} else {
log.error("JSON write failed: {} | path={}",
jme.getOriginalMessage(), jme.getPathReference(), jme);
log.error("JSON write failed: {} | path={} | details={}",
jme.getOriginalMessage(), jme.getPathReference(), describePath(path), jme);
}
} else {
log.error("JSON write failed: {} | path={}",
jme.getOriginalMessage(), jme.getPathReference(), jme);
log.error("JSON write failed because of path is smaller than 3: {} | path={} | details={}",
jme.getOriginalMessage(), jme.getPathReference(), describePath(path), jme);
}

} catch (Exception e) {
log.error("Unrecognized exception: ", e);
}
return super.handleHttpMessageNotWritable(exception, headers, status, request);
}

private static String describePath(List<JsonMappingException.Reference> path) {
if (path == null || path.isEmpty()) return "<empty>";
StringBuilder sb = new StringBuilder();
for (int i = 0; i < path.size(); i++) {
JsonMappingException.Reference ref = path.get(i);
Object from = ref.getFrom();

if (i > 0) sb.append(" | ");

sb.append(i).append(":");
sb.append(from == null ? "null" : from.getClass().getSimpleName());

if (ref.getFieldName() != null) sb.append(".").append(ref.getFieldName());
if (ref.getIndex() >= 0) sb.append("[").append(ref.getIndex()).append("]");

sb.append(" (").append(ref).append(")");
}
return sb.toString();
}

private static void tracePathAll(List<JsonMappingException.Reference> path) {
if (path == null || path.isEmpty()) {
log.trace("[JSON_WRITE][PATH] <empty>");
return;
}

for (int i = 0; i < path.size(); i++) {
JsonMappingException.Reference ref = path.get(i);
Object from = ref.getFrom();

String where = (ref.getFieldName() != null ? "." + ref.getFieldName() : "")
+ (ref.getIndex() >= 0 ? "[" + ref.getIndex() + "]" : "");

log.trace("[JSON_WRITE][PATH] idx={} fromType={}{} ref={} from={}",
i,
(from == null ? "null" : from.getClass().getName()),
where,
ref,
from);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@
@EqualsAndHashCode(callSuper = true)
public abstract class MapField extends TextField {

public static final String NONE_OPTION_KEY = "none";

protected List<String> keyValue;
protected Map<String, I18nString> keyValueTranslations;

Expand All @@ -21,7 +23,10 @@ public MapField(MapField field) {
this.keyValue = field.keyValue == null ? null : new ArrayList<>(field.keyValue);
this.keyValueTranslations = field.keyValueTranslations == null ? null
: field.keyValueTranslations.entrySet().stream()
.collect(Collectors.toMap(Map.Entry::getKey, entry -> new I18nString(entry.getValue())));
.collect(Collectors.toMap(entry ->
resolveTranslationPairKey(entry.getKey()),
entry -> new I18nString(entry.getValue()),
(existing, replacement) -> replacement, LinkedHashMap::new));
}

public MapField(Map.Entry<String, I18nString> valueTranslationPair) {
Expand All @@ -34,11 +39,15 @@ public MapField(List<Map.Entry<String, I18nString>> valueTranslationPairs) {
}
List<String> values = new ArrayList<>();
this.keyValue = new ArrayList<>();
this.keyValueTranslations = new HashMap<>();
this.keyValueTranslations = new LinkedHashMap<>();
for (Map.Entry<String, I18nString> valueTranslationPair : valueTranslationPairs) {
this.keyValue.add(valueTranslationPair.getKey());
String key = resolveTranslationPairKey(valueTranslationPair.getKey());
this.keyValue.add(key);
values.addAll(I18nStringUtils.collectTranslations(valueTranslationPair.getValue()));
this.keyValueTranslations.put(valueTranslationPair.getKey(), valueTranslationPair.getValue());
this.keyValueTranslations.put(
key,
valueTranslationPair.getValue() == null ? null : new I18nString(valueTranslationPair.getValue())
);
}
this.textValue = values;
this.fulltextValue = values;
Expand All @@ -52,4 +61,8 @@ public Object getValue() {
}
return null;
}

private String resolveTranslationPairKey(String key) {
return key == null || key.isBlank() ? NONE_OPTION_KEY : key;
}
}
Loading