• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

felleslosninger / einnsyn-backend / 23341388835

20 Mar 2026 11:45AM UTC coverage: 85.246% (+0.07%) from 85.174%
23341388835

push

github

web-flow
Merge pull request #616 from felleslosninger/EIN-4857-nedlasting-av-fulltekstdokument

EIN-4857: Fulltext downloads

2637 of 3537 branches covered (74.55%)

Branch coverage included in aggregate %.

7850 of 8765 relevant lines covered (89.56%)

3.74 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

93.91
src/main/java/no/einnsyn/backend/error/EInnsynExceptionHandler.java
1
package no.einnsyn.backend.error;
2

3
import com.google.gson.JsonParseException;
4
import io.micrometer.core.instrument.MeterRegistry;
5
import java.util.List;
6
import java.util.stream.Collectors;
7
import lombok.extern.slf4j.Slf4j;
8
import no.einnsyn.backend.common.exceptions.models.AuthenticationException;
9
import no.einnsyn.backend.common.exceptions.models.AuthorizationException;
10
import no.einnsyn.backend.common.exceptions.models.BadRequestException;
11
import no.einnsyn.backend.common.exceptions.models.ConflictException;
12
import no.einnsyn.backend.common.exceptions.models.EInnsynException;
13
import no.einnsyn.backend.common.exceptions.models.InternalServerErrorException;
14
import no.einnsyn.backend.common.exceptions.models.MethodNotAllowedException;
15
import no.einnsyn.backend.common.exceptions.models.NetworkException;
16
import no.einnsyn.backend.common.exceptions.models.NotFoundException;
17
import no.einnsyn.backend.common.exceptions.models.TooManyUnverifiedOrdersException;
18
import no.einnsyn.backend.common.exceptions.models.ValidationException;
19
import no.einnsyn.backend.common.exceptions.models.ValidationException.FieldError;
20
import org.jetbrains.annotations.NotNull;
21
import org.springframework.context.MessageSourceResolvable;
22
import org.springframework.dao.DataIntegrityViolationException;
23
import org.springframework.http.HttpHeaders;
24
import org.springframework.http.HttpStatus;
25
import org.springframework.http.HttpStatusCode;
26
import org.springframework.http.ResponseEntity;
27
import org.springframework.http.converter.HttpMessageNotReadableException;
28
import org.springframework.transaction.TransactionSystemException;
29
import org.springframework.web.HttpRequestMethodNotSupportedException;
30
import org.springframework.web.bind.MethodArgumentNotValidException;
31
import org.springframework.web.bind.annotation.ControllerAdvice;
32
import org.springframework.web.bind.annotation.ExceptionHandler;
33
import org.springframework.web.context.request.ServletWebRequest;
34
import org.springframework.web.context.request.WebRequest;
35
import org.springframework.web.method.annotation.HandlerMethodValidationException;
36
import org.springframework.web.servlet.NoHandlerFoundException;
37
import org.springframework.web.servlet.mvc.method.annotation.ResponseEntityExceptionHandler;
38

39
@ControllerAdvice
40
@Slf4j
4✔
41
public class EInnsynExceptionHandler extends ResponseEntityExceptionHandler {
42

43
  private static final String RESPONSE_STATUS = "responseStatus";
44
  private static final String METRIC_NAME = "ein_exception";
45
  private static final String METRIC_LEVEL_KEY = "level";
46
  private static final String METRIC_EXCEPTION_KEY = "exception";
47
  private static final String METRIC_LEVEL_WARNING = "warning";
48
  private static final String METRIC_LEVEL_ERROR = "error";
49

50
  private final MeterRegistry meterRegistry;
51

52
  public EInnsynExceptionHandler(MeterRegistry meterRegistry) {
53
    super();
2✔
54
    this.meterRegistry = meterRegistry;
3✔
55
  }
1✔
56

57
  private void logAndCountWarning(EInnsynException ex, HttpStatusCode statusCode) {
58
    var exceptionName = ex.getClass().getSimpleName();
4✔
59
    log.atWarn()
3✔
60
        .setMessage(ex.getMessage())
4✔
61
        .addKeyValue(RESPONSE_STATUS, String.valueOf(statusCode))
2✔
62
        .log();
1✔
63
    meterRegistry
21✔
64
        .counter(
1✔
65
            METRIC_NAME,
66
            METRIC_LEVEL_KEY,
67
            METRIC_LEVEL_WARNING,
68
            METRIC_EXCEPTION_KEY,
69
            exceptionName)
70
        .increment();
1✔
71
  }
1✔
72

73
  private void logAndCountError(EInnsynException ex, HttpStatusCode statusCode) {
74
    var exceptionName = ex.getClass().getSimpleName();
4✔
75
    log.atError()
3✔
76
        .setMessage(ex.getMessage())
4✔
77
        .addKeyValue(RESPONSE_STATUS, String.valueOf(statusCode))
3✔
78
        .setCause(ex)
1✔
79
        .log();
1✔
80
    meterRegistry
21✔
81
        .counter(
1✔
82
            METRIC_NAME, METRIC_LEVEL_KEY, METRIC_LEVEL_ERROR, METRIC_EXCEPTION_KEY, exceptionName)
83
        .increment();
1✔
84
  }
1✔
85

86
  @ExceptionHandler(Exception.class)
87
  public ResponseEntity<Object> handleException(Exception ex) {
88
    var httpStatus = HttpStatus.INTERNAL_SERVER_ERROR;
2✔
89
    var internalServerErrorException =
6✔
90
        new InternalServerErrorException("Internal server error", ex);
91
    logAndCountError(internalServerErrorException, httpStatus);
4✔
92
    return new ResponseEntity<>(internalServerErrorException.toClientResponse(), null, httpStatus);
8✔
93
  }
94

95
  @ExceptionHandler(TransactionSystemException.class)
96
  public ResponseEntity<Object> handleException(TransactionSystemException ex) {
97
    if (ex.getRootCause() instanceof EInnsynException eInnsynException) {
9✔
98
      return handleException(eInnsynException);
4✔
99
    }
100

101
    var httpStatus = HttpStatus.INTERNAL_SERVER_ERROR;
2✔
102
    var internalServerErrorException =
6✔
103
        new InternalServerErrorException("Transaction system exception", ex);
104
    logAndCountError(internalServerErrorException, httpStatus);
4✔
105
    var clientResponse = internalServerErrorException.toClientResponse();
3✔
106
    return new ResponseEntity<>(clientResponse, null, httpStatus);
7✔
107
  }
108

109
  /**
110
   * 400 Bad Request
111
   *
112
   * @param ex The exception
113
   * @return The response entity
114
   */
115
  @ExceptionHandler(IllegalArgumentException.class)
116
  public ResponseEntity<Object> handleException(IllegalArgumentException ex) {
117
    var httpStatus = HttpStatus.BAD_REQUEST;
2✔
118
    var badRequestException = new BadRequestException(ex.getMessage(), ex);
7✔
119
    logAndCountWarning(badRequestException, httpStatus);
4✔
120
    var clientResponse = badRequestException.toClientResponse();
3✔
121
    return new ResponseEntity<>(clientResponse, null, httpStatus);
7✔
122
  }
123

124
  /**
125
   * 400 Bad Request
126
   *
127
   * @param ex The exception
128
   * @return The response entity
129
   */
130
  @ExceptionHandler(BadRequestException.class)
131
  public ResponseEntity<Object> handleException(BadRequestException ex) {
132
    var httpStatus = HttpStatus.BAD_REQUEST;
2✔
133
    logAndCountWarning(ex, httpStatus);
4✔
134
    var clientResponse = ex.toClientResponse();
3✔
135
    return new ResponseEntity<>(clientResponse, null, httpStatus);
7✔
136
  }
137

138
  /**
139
   * 403 Forbidden
140
   *
141
   * @param ex The exception
142
   * @return The response entity
143
   */
144
  @ExceptionHandler(AuthorizationException.class)
145
  public ResponseEntity<Object> handleException(AuthorizationException ex) {
146
    var httpStatus = HttpStatus.FORBIDDEN;
2✔
147
    logAndCountWarning(ex, httpStatus);
4✔
148
    var clientResponse = ex.toClientResponse();
3✔
149
    return new ResponseEntity<>(clientResponse, null, httpStatus);
7✔
150
  }
151

152
  /**
153
   * 401 Unauthorized
154
   *
155
   * @param ex The exception
156
   * @return The response entity
157
   */
158
  @ExceptionHandler(AuthenticationException.class)
159
  public ResponseEntity<Object> handleException(AuthenticationException ex) {
160
    var httpStatus = HttpStatus.UNAUTHORIZED;
2✔
161
    logAndCountWarning(ex, httpStatus);
4✔
162
    var clientResponse = ex.toClientResponse();
3✔
163
    return new ResponseEntity<>(clientResponse, null, httpStatus);
7✔
164
  }
165

166
  /*
167
   * 404 Not Found
168
   *
169
   * @param ex The exception
170
   * @return The response entity
171
   */
172
  @ExceptionHandler(NotFoundException.class)
173
  public ResponseEntity<Object> handleException(NotFoundException ex) {
174
    var httpStatus = HttpStatus.NOT_FOUND;
2✔
175
    logAndCountWarning(ex, httpStatus);
4✔
176
    var clientResponse = ex.toClientResponse();
3✔
177
    return new ResponseEntity<>(clientResponse, null, httpStatus);
7✔
178
  }
179

180
  /**
181
   * 502 Bad Gateway
182
   *
183
   * @param ex The exception
184
   * @return The response entity
185
   */
186
  @ExceptionHandler(NetworkException.class)
187
  public ResponseEntity<Object> handleException(NetworkException ex) {
188
    var httpStatus = HttpStatus.BAD_GATEWAY;
2✔
189
    logAndCountError(ex, httpStatus);
4✔
190
    var clientResponse = ex.toClientResponse();
3✔
191
    return new ResponseEntity<>(clientResponse, null, httpStatus);
7✔
192
  }
193

194
  /**
195
   * DataIntegrityViolation
196
   *
197
   * <p>Likely a unique constraint violation, foreign key constraint violation, not-null constraint
198
   * or similar.
199
   *
200
   * @param ex The exception
201
   */
202
  @ExceptionHandler(DataIntegrityViolationException.class)
203
  public ResponseEntity<Object> handleDataIntegrityViolationException(Exception ex) {
204
    var httpStatus = HttpStatus.INTERNAL_SERVER_ERROR;
2✔
205
    var internalServerErrorException =
6✔
206
        new InternalServerErrorException("Data integrity violation", ex);
207
    logAndCountError(internalServerErrorException, httpStatus);
4✔
208
    var clientResponse = internalServerErrorException.toClientResponse();
3✔
209
    return new ResponseEntity<>(clientResponse, null, httpStatus);
7✔
210
  }
211

212
  /**
213
   * Conflict
214
   *
215
   * @param ex The exception
216
   */
217
  @ExceptionHandler(ConflictException.class)
218
  public ResponseEntity<Object> handleConflictException(ConflictException ex) {
219
    var httpStatus = HttpStatus.CONFLICT;
2✔
220
    logAndCountWarning(ex, httpStatus);
4✔
221
    var clientResponse = ex.toClientResponse();
3✔
222
    return new ResponseEntity<>(clientResponse, null, httpStatus);
7✔
223
  }
224

225
  /**
226
   * Too many unverified orders.
227
   *
228
   * @param ex the exception
229
   * @return the response entity
230
   */
231
  @ExceptionHandler(TooManyUnverifiedOrdersException.class)
232
  public ResponseEntity<Object> handleTooManyUnverifiedOrdersException(
233
      TooManyUnverifiedOrdersException ex) {
234
    var httpStatus = HttpStatus.TOO_MANY_REQUESTS;
2✔
235
    logAndCountWarning(ex, httpStatus);
4✔
236
    var clientResponse = ex.toClientResponse();
3✔
237
    return new ResponseEntity<>(clientResponse, null, httpStatus);
7✔
238
  }
239

240
  /**
241
   * Input validation errors, @Valid and @Validated on JSON fields.
242
   *
243
   * @param ex The exception
244
   * @param headers The headers
245
   * @param status The status
246
   * @param request The request
247
   */
248
  @Override
249
  protected ResponseEntity<Object> handleMethodArgumentNotValid(
250
      @NotNull MethodArgumentNotValidException ex,
251
      @NotNull HttpHeaders headers,
252
      @NotNull HttpStatusCode status,
253
      @NotNull WebRequest request) {
254

255
    var fieldErrors =
1✔
256
        ex.getFieldErrors().stream()
3✔
257
            .map(
1✔
258
                e ->
259
                    new FieldError(
4✔
260
                        e.getField(),
1✔
261
                        e.getRejectedValue() == null ? null : e.getRejectedValue().toString(),
9✔
262
                        e.getDefaultMessage()))
2✔
263
            .toList();
2✔
264

265
    var httpStatus = HttpStatus.BAD_REQUEST;
2✔
266
    var fieldErrorString =
1✔
267
        fieldErrors.stream()
2✔
268
            .map(f -> "\"" + f.getFieldName() + "\": \"" + f.getValue() + "\"")
8✔
269
            .collect(Collectors.joining(", "));
4✔
270
    var validationException =
8✔
271
        new ValidationException(
272
            "Field validation error on fields: " + fieldErrorString, ex, fieldErrors);
273

274
    logAndCountWarning(validationException, httpStatus);
4✔
275
    var clientResponse = validationException.toClientResponse();
3✔
276
    return handleExceptionInternal(
8✔
277
        validationException, clientResponse, headers, httpStatus, request);
278
  }
279

280
  /**
281
   * 404
282
   *
283
   * <p>When no handler is found for the request.
284
   *
285
   * @param ex The exception
286
   * @param headers The headers
287
   * @param status The status
288
   * @param request The request
289
   */
290
  @Override
291
  protected ResponseEntity<Object> handleNoHandlerFoundException(
292
      @NotNull NoHandlerFoundException ex,
293
      @NotNull HttpHeaders headers,
294
      @NotNull HttpStatusCode status,
295
      @NotNull WebRequest request) {
296
    var httpStatus = HttpStatus.NOT_FOUND;
2✔
297
    var uri =
298
        (request instanceof ServletWebRequest servletWebRequest)
6!
299
            ? servletWebRequest.getRequest().getRequestURI()
4✔
300
            : request.getDescription(false);
1✔
301
    var notFoundException = new NotFoundException("No handler found: " + uri, ex);
7✔
302
    logAndCountWarning(notFoundException, httpStatus);
4✔
303
    var clientResponse = notFoundException.toClientResponse();
3✔
304
    return handleExceptionInternal(notFoundException, clientResponse, headers, httpStatus, request);
8✔
305
  }
306

307
  /**
308
   * Path-variable validation errors. Most likely non-existent IDs.
309
   *
310
   * @param ex The exception
311
   * @param headers The headers
312
   * @param status The status
313
   * @param request The request
314
   */
315
  @Override
316
  protected ResponseEntity<Object> handleHandlerMethodValidationException(
317
      @NotNull HandlerMethodValidationException ex,
318
      @NotNull HttpHeaders headers,
319
      @NotNull HttpStatusCode status,
320
      @NotNull WebRequest request) {
321

322
    var notFound = false;
2✔
323
    for (var error : ex.getAllErrors()) {
11✔
324
      var defaultMessage = error.getDefaultMessage();
3✔
325
      if (defaultMessage != null && defaultMessage.contains("not found")) {
6!
326
        notFound = true;
2✔
327
        break;
1✔
328
      }
329
    }
1✔
330

331
    // 404
332
    if (notFound) {
2✔
333
      var httpStatus = HttpStatus.NOT_FOUND;
2✔
334
      var notFoundException =
4✔
335
          new NotFoundException("Not found: " + request.getDescription(false), ex);
5✔
336
      logAndCountWarning(notFoundException, httpStatus);
4✔
337
      var clientResponse = notFoundException.toClientResponse();
3✔
338
      return handleExceptionInternal(
8✔
339
          notFoundException, clientResponse, headers, httpStatus, request);
340
    }
341

342
    // Bad request
343
    else {
344
      var httpStatus = HttpStatus.BAD_REQUEST;
2✔
345

346
      var errorMessages = buildValidationMessages(ex);
4✔
347
      var userMessage = summarizeValidationMessages(errorMessages);
4✔
348
      var badRequestException = new BadRequestException(userMessage, ex);
6✔
349
      var exceptionName = badRequestException.getClass().getSimpleName();
4✔
350
      log.atWarn()
3✔
351
          .setMessage(badRequestException.getMessage())
4✔
352
          .addKeyValue(RESPONSE_STATUS, String.valueOf(httpStatus))
4✔
353
          .addKeyValue("validationErrors", errorMessages)
4✔
354
          .addKeyValue("request", request.getDescription(false))
2✔
355
          .log();
1✔
356
      meterRegistry
21✔
357
          .counter(
1✔
358
              METRIC_NAME,
359
              METRIC_LEVEL_KEY,
360
              METRIC_LEVEL_WARNING,
361
              METRIC_EXCEPTION_KEY,
362
              exceptionName)
363
          .increment();
1✔
364
      var clientResponse = badRequestException.toClientResponse();
3✔
365
      return handleExceptionInternal(
8✔
366
          badRequestException, clientResponse, headers, httpStatus, request);
367
    }
368
  }
369

370
  /**
371
   * JSON parse errors
372
   *
373
   * @param ex The exception
374
   * @param headers The headers
375
   * @param status The status
376
   * @param request The request
377
   */
378
  @Override
379
  protected ResponseEntity<Object> handleHttpMessageNotReadable(
380
      @NotNull HttpMessageNotReadableException ex,
381
      @NotNull HttpHeaders headers,
382
      @NotNull HttpStatusCode status,
383
      @NotNull WebRequest request) {
384

385
    if (ex.getCause() instanceof JsonParseException jsonParseException
9!
386
        && jsonParseException.getCause() instanceof BadRequestException badRequestException) {
9✔
387
      return handleException(badRequestException);
4✔
388
    }
389

390
    var httpStatus = HttpStatus.BAD_REQUEST;
2✔
391
    var badRequestException = new BadRequestException(ex.getMessage(), ex);
7✔
392
    logAndCountWarning(badRequestException, httpStatus);
4✔
393

394
    // Don't send standard message, HttpMessageNotReadableException may contain valuable info:
395
    var censoredBadRequestException = new BadRequestException("Failed to parse the request.", ex);
6✔
396
    var clientResponse = censoredBadRequestException.toClientResponse();
3✔
397
    return handleExceptionInternal(
8✔
398
        badRequestException, clientResponse, headers, httpStatus, request);
399
  }
400

401
  @Override
402
  protected ResponseEntity<Object> handleHttpRequestMethodNotSupported(
403
      HttpRequestMethodNotSupportedException ex,
404
      HttpHeaders headers,
405
      HttpStatusCode status,
406
      WebRequest request) {
407

408
    var supportedMethods = ex.getSupportedHttpMethods();
3✔
409
    var methodNotAllowedException =
3✔
410
        new MethodNotAllowedException(
411
            "Method "
412
                + ex.getMethod()
6✔
413
                + " is not supported for this endpoint. Supported methods are: "
414
                + supportedMethods);
415

416
    // Add Allow header with supported methods
417
    if (supportedMethods != null) {
2!
418
      headers.setAllow(supportedMethods);
3✔
419
    }
420

421
    var errorResponse = methodNotAllowedException.toClientResponse();
3✔
422
    return handleExceptionInternal(
8✔
423
        methodNotAllowedException, errorResponse, headers, status, request);
424
  }
425

426
  private List<String> buildValidationMessages(HandlerMethodValidationException ex) {
427
    return ex.getAllErrors().stream()
6✔
428
        .map(this::resolveValidationMessage)
2✔
429
        .filter(message -> message != null && !message.isBlank())
9!
430
        .toList();
1✔
431
  }
432

433
  private String summarizeValidationMessages(List<String> messages) {
434
    if (messages.isEmpty()) {
3!
435
      return "Validation failed.";
×
436
    }
437
    var limit = 5;
2✔
438
    var summary = messages.stream().limit(limit).collect(Collectors.joining("; "));
10✔
439
    if (messages.size() > limit) {
4✔
440
      summary += "; and " + (messages.size() - limit) + " more.";
7✔
441
    }
442
    return "Validation failed: " + summary;
3✔
443
  }
444

445
  private String resolveValidationMessage(MessageSourceResolvable error) {
446
    var defaultMessage = error.getDefaultMessage();
3✔
447
    if (defaultMessage != null && !defaultMessage.isBlank()) {
5!
448
      return defaultMessage;
2✔
449
    }
450
    var codes = error.getCodes();
3✔
451
    if (codes != null && codes.length > 0) {
5!
452
      return codes[0];
4✔
453
    }
454
    return "Validation error";
×
455
  }
456
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc