Skip to content

Commit

Permalink
EA-3236 add the error handling for record api and other validations
Browse files Browse the repository at this point in the history
  • Loading branch information
SrishtiSingh-eu committed Aug 28, 2023
1 parent 9589d97 commit 840a335
Show file tree
Hide file tree
Showing 10 changed files with 321 additions and 24 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@

import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.springframework.boot.web.servlet.support.SpringBootServletInitializer;

/**
* Main application. Allows deploying as a war and logs instance data when deployed in Cloud Foundry
Expand All @@ -22,7 +23,7 @@
// DataSources are manually configured (for EM and batch DBs)
DataSourceAutoConfiguration.class
})
public class RecordApp {
public class RecordApp extends SpringBootServletInitializer {

private static final Logger LOG = LogManager.getLogger(RecordApp.class);

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
package eu.europeana.api.record.exception;

import eu.europeana.api.record.model.ApiErrorResponse;
import jakarta.servlet.http.HttpServletRequest;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.RestControllerAdvice;

/**
* Global exception handler that catches all errors and logs the interesting ones
*
* @author Srishti Singh
* Created on 24-08-2023
*/
@RestControllerAdvice
@ResponseBody
class GlobalExceptionHandler {

private static final Logger LOG = LogManager.getLogger(GlobalExceptionHandler.class);

// with Spring boot 3 (and Spring Framework 6) require a baseline of Jakarte EE 10
// You cannot use it with Java EE or Jakarte EE versions below that.
// You have to remove the explicit dependency on jakarta.servlet-api from your pom.xml.
// Java Servlet 4 is below the baseline and in particular still uses the package names starting with javax.servlet.
// If you remove the explicit dependency, Spring will pull in transitively the correct one.
// You then need to replace all imports starting with javax.servlet with javax replaced by jakarta

protected void logException(RecordApiException e) {
if (e.doLog()) {
if (e.doLogStacktrace()) {
LOG.error("Caught exception", e);
} else {
LOG.error("Caught exception: {}", e.getMessage());
}
}

}


@ExceptionHandler
public ResponseEntity<ApiErrorResponse> handleBaseException(RecordApiException e, HttpServletRequest httpRequest) {
this.logException(e);
ApiErrorResponse response = (new ApiErrorResponse.Builder(httpRequest, e, false)).setStatus(e.getResponseStatus().value()).setError
(e.getResponseStatus().getReasonPhrase()).setMessage(e.doExposeMessage() ? e.getMessage() : null).setCode(e.getErrorCode()).build();
return ResponseEntity.status(e.getResponseStatus()).headers(this.createHttpHeaders()).body(response);
}

protected HttpHeaders createHttpHeaders() {
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_JSON);
return headers;
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package eu.europeana.api.record.exception;

import org.springframework.http.HttpStatus;

/** Exception thrown when an error occurs due to bad user input. */
public class HttpBadRequestException extends RecordApiException {

public HttpBadRequestException(String msg) {
super(msg);
}

@Override
public boolean doLogStacktrace() {
return false;
}

@Override
public HttpStatus getResponseStatus() {
return HttpStatus.BAD_REQUEST;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package eu.europeana.api.record.exception;

import org.springframework.http.HttpStatus;

/** Exception thrown when a record already exists in the DB. */
public class RecordAlreadyExistsException extends RecordApiException {

public RecordAlreadyExistsException(String about) {
super("Record already exists for '" + about);
}

@Override
public boolean doLogStacktrace() {
return false;
}

@Override
public HttpStatus getResponseStatus() {
return HttpStatus.BAD_REQUEST;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
package eu.europeana.api.record.exception;

import org.springframework.http.HttpStatus;

public class RecordApiException extends Exception {

private static final long serialVersionUID = -1354471712894853562L;
private final String errorCode;

public RecordApiException(String msg, Throwable t) {
this(msg, (String)null, t);
}

public RecordApiException(String msg, String errorCode, Throwable t) {
super(msg, t);
this.errorCode = errorCode;
}

public RecordApiException(String msg) {
super(msg);
this.errorCode = null;
}

public RecordApiException(String msg, String errorCode) {
super(msg);
this.errorCode = errorCode;
}

public boolean doLog() {
return true;
}

public boolean doLogStacktrace() {
return true;
}

public String getErrorCode() {
return this.errorCode;
}

public boolean doExposeMessage() {
return true;
}

public HttpStatus getResponseStatus() {
return HttpStatus.INTERNAL_SERVER_ERROR;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
package eu.europeana.api.record.model;

import com.fasterxml.jackson.annotation.JsonFormat;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.annotation.JsonPropertyOrder;
import jakarta.servlet.http.HttpServletRequest;
import org.springframework.util.StringUtils;

import java.time.OffsetDateTime;
import java.util.List;

@JsonPropertyOrder({"success", "status", "error", "message", "timestamp", "path"})
@JsonInclude(JsonInclude.Include.NON_EMPTY)
public class ApiErrorResponse {
private final boolean success = false;
private final int status;
private final String error;
private final String message;
@JsonFormat(
pattern = "yyyy-MM-dd'T'HH:mm:ss'Z'"
)
private final OffsetDateTime timestamp = OffsetDateTime.now();
private final String path;
private final String code;

private ApiErrorResponse(int status, String error, String message, String path, String code) {
this.status = status;
this.error = error;
this.message = message;
this.path = path;
this.code = code;
}

public String getError() {
return this.error;
}

public boolean isSuccess() {
return false;
}

public int getStatus() {
return this.status;
}

public String getMessage() {
return this.message;
}

public OffsetDateTime getTimestamp() {
return this.timestamp;
}

public String getPath() {
return this.path;
}

public String getCode() {
return this.code;
}

public static class Builder {
private int status;
private String message;
private String error;
private final String path;
private String code;

public Builder(HttpServletRequest httpRequest, Exception e, boolean stacktraceEnabled) {
this.path = getRequestPath(httpRequest);
boolean includeErrorStack = false;
String profileParamString = httpRequest.getParameter("profile");
if (StringUtils.hasLength(profileParamString)) {
includeErrorStack = List.of(profileParamString.split(",")).contains("debug");
}

}

public ApiErrorResponse.Builder setStatus(int status) {
this.status = status;
return this;
}

public ApiErrorResponse.Builder setMessage(String message) {
this.message = message;
return this;
}

public ApiErrorResponse.Builder setError(String error) {
this.error = error;
return this;
}

public ApiErrorResponse.Builder setCode(String code) {
this.code = code;
return this;
}

public ApiErrorResponse build() {
return new ApiErrorResponse(this.status, this.error, this.message, this.path, this.code);
}
}

public static String getRequestPath(HttpServletRequest httpRequest) {
return httpRequest.getQueryString() == null ? String.valueOf(httpRequest.getRequestURL()) : String.valueOf(httpRequest.getRequestURL().append("?").append(httpRequest.getQueryString()));
}
}

Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,12 @@ public Record saveRecord(Record record) {
return recordRepository.save(record);
}

public Record getRecord(String recordId) {
return recordRepository.findById(recordId);
public Record getRecord(String about) {
return recordRepository.findById(about);
}

public boolean existsByID(String about) {
return recordRepository.existsByRecordId(about);
}

}
Original file line number Diff line number Diff line change
@@ -1,22 +1,30 @@
package eu.europeana.api.record.web;

import eu.europeana.api.commons.web.http.HttpHeaders;
import eu.europeana.api.record.exception.HttpBadRequestException;
import eu.europeana.api.record.exception.RecordAlreadyExistsException;
import eu.europeana.api.record.exception.RecordApiException;
import eu.europeana.api.record.impl.RecordImpl;
import eu.europeana.api.record.model.Record;
import eu.europeana.api.record.service.RecordService;
import io.swagger.annotations.ApiOperation;
import jakarta.servlet.http.HttpServletRequest;
import org.apache.commons.lang3.StringUtils;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;

import jakarta.servlet.http.HttpServletRequest;

@RestController
@Validated
public class RecordController {

private static final Logger LOGGER = LogManager.getLogger(RecordController.class);

private final RecordService recordService;

@Autowired
Expand All @@ -33,19 +41,23 @@ public RecordController(RecordService recordService) {
value = "/record/",
produces = {MediaType.APPLICATION_JSON_VALUE, HttpHeaders.CONTENT_TYPE_JSONLD})
public ResponseEntity<String> registerRecord(
@RequestBody RecordImpl record, HttpServletRequest request) throws Exception {

// TODO validation for language tagged values to be single

System.out.println(record.getProxies().get(0));
System.out.println(record.getProxies().get(1));

System.out.println(record.getAggregation());
System.out.println(record.getAgents().get(0));
System.out.println(record.getAgents().get(1));
@RequestBody RecordImpl record, HttpServletRequest request) throws RecordApiException {

// TODO request validation for language tagged values to be single
// and others validation yet to be added
if (StringUtils.isNotEmpty(record.getAbout())) {
LOGGER.debug("Registering new Record={}", record.getAbout());
} else {
// id is mandatory in request body
throw new HttpBadRequestException("Mandatory field missing in the request body: id");
}

// check if Record already exists
if (recordService.existsByID(record.getAbout())) {
throw new RecordAlreadyExistsException(record.getAbout());
}

recordService.saveRecord(record);

return new ResponseEntity<>(HttpStatus.OK);
}

Expand All @@ -69,14 +81,6 @@ public ResponseEntity<String> retrieveRecord (
String about = "http://data.europeana.eu/item/" + collectionId + "/" + recordId;
System.out.println("about " +about);
RecordImpl record = (RecordImpl) recordService.getRecord(about);

System.out.println(record.getProxies().get(0));
System.out.println(record.getProxies().get(1));

System.out.println(record.getAggregation());
System.out.println(record.getAgents().get(0));
System.out.println(record.getAgents().get(1));

return new ResponseEntity(HttpStatus.OK);
}

Expand Down
11 changes: 11 additions & 0 deletions record-api-web/src/main/resources/application.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
server.port: 8080

spring:
application:
name: Record API

server:
error:
include-message: always
include-stacktrace: on_param
include-exception: false
Loading

0 comments on commit 840a335

Please sign in to comment.