diff --git a/record-api-common/pom.xml b/record-api-common/pom.xml
index b432440..701ed45 100644
--- a/record-api-common/pom.xml
+++ b/record-api-common/pom.xml
@@ -16,4 +16,64 @@
${java.version}
+
+
+
+ org.apache.commons
+ commons-lang3
+ ${apache.commomLang3.version}
+
+
+
+ com.fasterxml.jackson.dataformat
+ jackson-dataformat-xml
+ 2.15.3
+
+
+
+
+ org.springframework.boot
+ spring-boot-autoconfigure
+ ${spring-boot.version}
+ provided
+
+
+ org.springframework
+ spring-web
+ ${spring-framework.version}
+ provided
+
+
+ org.springframework
+ spring-webmvc
+ ${spring-framework.version}
+ provided
+
+
+
+ jakarta.validation
+ jakarta.validation-api
+ 3.0.2
+
+
+
+ jakarta.servlet
+ jakarta.servlet-api
+ 6.0.0
+ provided
+
+
+
+ javax.annotation
+ javax.annotation-api
+ 1.3.2
+
+
+
+ org.springframework.boot
+ spring-boot-starter-log4j2
+
+
+
+
\ No newline at end of file
diff --git a/record-api-common/src/main/java/eu/europeana/api/config/MediaTypeConfig.java b/record-api-common/src/main/java/eu/europeana/api/config/MediaTypeConfig.java
new file mode 100644
index 0000000..a799540
--- /dev/null
+++ b/record-api-common/src/main/java/eu/europeana/api/config/MediaTypeConfig.java
@@ -0,0 +1,51 @@
+package eu.europeana.api.config;
+
+import com.fasterxml.jackson.dataformat.xml.XmlMapper;
+import eu.europeana.api.model.MediaType;
+import eu.europeana.api.model.MediaTypes;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+
+import javax.annotation.Resource;
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.util.stream.Collectors;
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.Logger;
+
+
+/**
+ * @author srishti singh
+ * @since 18 October 2023
+ */
+@Configuration
+public class MediaTypeConfig {
+
+ private static final Logger LOG = LogManager.getLogger(MediaTypeConfig.class);
+
+ @Resource(name = "msXmlMapper")
+ private XmlMapper xmlMapper;
+
+
+ @Bean(name = "msMediaTypes")
+ public MediaTypes getMediaTypes() throws IOException {
+
+ MediaTypes mediaTypes;
+ try (InputStream inputStream = getClass().getResourceAsStream("/mediacategories.xml")) {
+ assert inputStream != null;
+ try (BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream))) {
+ String contents = reader.lines().collect(Collectors.joining(System.lineSeparator()));
+ mediaTypes = xmlMapper.readValue(contents, MediaTypes.class);
+ }
+ }
+
+ if (!mediaTypes.mediaTypeCategories.isEmpty()) {
+ mediaTypes.getMap().putAll(mediaTypes.mediaTypeCategories.stream().filter(media -> !media.isEuScreen()).collect(Collectors.toMap(MediaType::getMimeType, e-> e)));
+ } else {
+ LOG.error("media Categories not configured at startup. mediacategories.xml file not added or is empty");
+ }
+ return mediaTypes;
+ }
+}
diff --git a/record-api-common/src/main/java/eu/europeana/api/config/SerialisationConfig.java b/record-api-common/src/main/java/eu/europeana/api/config/SerialisationConfig.java
new file mode 100644
index 0000000..1ae06ad
--- /dev/null
+++ b/record-api-common/src/main/java/eu/europeana/api/config/SerialisationConfig.java
@@ -0,0 +1,21 @@
+package eu.europeana.api.config;
+
+import com.fasterxml.jackson.dataformat.xml.XmlMapper;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+
+import java.text.DateFormat;
+import java.text.SimpleDateFormat;
+
+@Configuration
+public class SerialisationConfig {
+
+ private final DateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSSXX");
+
+ @Bean("msXmlMapper")
+ public XmlMapper xmlMapper() {
+ XmlMapper xmlMapper = new XmlMapper();
+ xmlMapper.setDateFormat(dateFormat);
+ return xmlMapper;
+ }
+}
diff --git a/record-api-common/src/main/java/eu/europeana/api/error/EuropeanaApiErrorAttributes.java b/record-api-common/src/main/java/eu/europeana/api/error/EuropeanaApiErrorAttributes.java
new file mode 100644
index 0000000..7c83ee6
--- /dev/null
+++ b/record-api-common/src/main/java/eu/europeana/api/error/EuropeanaApiErrorAttributes.java
@@ -0,0 +1,66 @@
+package eu.europeana.api.error;
+
+
+import org.springframework.boot.web.error.ErrorAttributeOptions;
+import org.springframework.boot.web.servlet.error.DefaultErrorAttributes;
+import org.springframework.stereotype.Component;
+import org.springframework.util.StringUtils;
+import org.springframework.web.context.request.WebRequest;
+
+import java.time.OffsetDateTime;
+import java.util.Iterator;
+import java.util.LinkedHashMap;
+import java.util.Map;
+
+import static eu.europeana.api.error.EuropeanaErrorConstants.*;
+
+@Component
+public class EuropeanaApiErrorAttributes extends DefaultErrorAttributes {
+
+ /**
+ * Used by Spring to display errors with no custom handler.
+ * Since we explicitly return {@link EuropeanaApiErrorResponse} on errors within controllers, this method is only invoked when
+ * a request isn't handled by any controller.
+ */
+ @Override
+ public Map getErrorAttributes(WebRequest webRequest, ErrorAttributeOptions sbOptions) {
+ final Map defaultErrorAttributes = super.getErrorAttributes(webRequest, sbOptions);
+
+ // use LinkedHashMap to guarantee display order
+ LinkedHashMap europeanaErrorAttributes = new LinkedHashMap<>();
+ europeanaErrorAttributes.put(SUCCESS, false);
+ europeanaErrorAttributes.put(STATUS, defaultErrorAttributes.get(STATUS));
+ europeanaErrorAttributes.put(ERROR, defaultErrorAttributes.get(ERROR));
+ // message not shown
+ europeanaErrorAttributes.put(TIMESTAMP, OffsetDateTime.now());
+ addPathRequestParameters(europeanaErrorAttributes, webRequest);
+ return europeanaErrorAttributes;
+ }
+
+
+ /**
+ * Spring errors only return the error path and not the parameters, so we add those ourselves.
+ * The original parameter string is not available in WebRequest so we rebuild it.
+ */
+ private void addPathRequestParameters(Map errorAttributes, WebRequest webRequest) {
+ Iterator it = webRequest.getParameterNames();
+ StringBuilder s = new StringBuilder();
+ while (it.hasNext()) {
+ if (s.length() == 0) {
+ s.append('?');
+ } else {
+ s.append("&");
+ }
+ String paramName = it.next();
+ s.append(paramName);
+ String paramValue = webRequest.getParameter(paramName);
+ if (StringUtils.hasText(paramValue)) {
+ s.append("=").append(paramValue);
+ }
+ }
+
+ if (s.length() > 0) {
+ errorAttributes.put(PATH, errorAttributes.get(PATH) + s.toString());
+ }
+ }
+}
diff --git a/record-api-common/src/main/java/eu/europeana/api/error/EuropeanaApiErrorController.java b/record-api-common/src/main/java/eu/europeana/api/error/EuropeanaApiErrorController.java
new file mode 100644
index 0000000..d9c0edf
--- /dev/null
+++ b/record-api-common/src/main/java/eu/europeana/api/error/EuropeanaApiErrorController.java
@@ -0,0 +1,76 @@
+package eu.europeana.api.error;
+
+import java.util.Map;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.boot.autoconfigure.web.ErrorProperties;
+import org.springframework.boot.autoconfigure.web.servlet.error.AbstractErrorController;
+import org.springframework.boot.web.error.ErrorAttributeOptions;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.RestController;
+import org.springframework.web.context.request.ServletWebRequest;
+import org.springframework.web.context.request.WebRequest;
+import jakarta.servlet.http.HttpServletRequest;
+
+import javax.annotation.PostConstruct;;
+
+@RestController
+public class EuropeanaApiErrorController extends AbstractErrorController {
+
+ private final EuropeanaApiErrorAttributes errorAttributes;
+ private ErrorAttributeOptions errorAttributeOptions = ErrorAttributeOptions.defaults();
+
+ @Value("${server.error.include-message}")
+ private ErrorProperties.IncludeAttribute includeMessage;
+ @Value("${server.error.include-exception}")
+ private Boolean includeException;
+ @Value("${server.error.include-stacktrace}")
+ private ErrorProperties.IncludeStacktrace includeStacktrace;
+
+ /**
+ * Initialize a new controller to handle error output
+ * @param errorAttributes auto-wired ApiErrorAttributes (error fields)
+ */
+ @Autowired
+ public EuropeanaApiErrorController(EuropeanaApiErrorAttributes errorAttributes) {
+ super(errorAttributes);
+ this.errorAttributes = errorAttributes;
+ }
+
+ @PostConstruct
+ private void init() {
+ if (ErrorProperties.IncludeAttribute.ALWAYS.equals(includeMessage)) {
+ errorAttributeOptions = ErrorAttributeOptions.of(ErrorAttributeOptions.Include.MESSAGE);
+ }
+ if (includeException) {
+ errorAttributeOptions = ErrorAttributeOptions.of(ErrorAttributeOptions.Include.EXCEPTION);
+ }
+ if (ErrorProperties.IncludeStacktrace.ALWAYS.equals(includeStacktrace)) {
+ errorAttributeOptions = ErrorAttributeOptions.of(ErrorAttributeOptions.Include.STACK_TRACE);
+ }
+ }
+
+// @Override
+// public String getErrorPath() {
+// return "/error";
+// }
+
+ /**
+ * Override default Spring-Boot error endpoint
+ * @param request incoming request
+ * @return error object to serialize
+ */
+ @GetMapping("/error")
+ public Map error(HttpServletRequest request) {
+ ErrorAttributeOptions options = errorAttributeOptions;
+ if (ErrorProperties.IncludeAttribute.ON_PARAM.equals(includeMessage) && this.getMessageParameter(request)) {
+ options = errorAttributeOptions.including(ErrorAttributeOptions.Include.MESSAGE);
+ }
+ if (ErrorProperties.IncludeStacktrace.ON_PARAM.equals(includeStacktrace) && this.getTraceParameter(request)) {
+ options = errorAttributeOptions.including(ErrorAttributeOptions.Include.STACK_TRACE);
+ }
+ WebRequest webRequest = new ServletWebRequest(request);
+ return this.errorAttributes.getErrorAttributes(webRequest, options);
+ }
+
+}
\ No newline at end of file
diff --git a/record-api-web/src/main/java/eu/europeana/api/record/model/ApiErrorResponse.java b/record-api-common/src/main/java/eu/europeana/api/error/EuropeanaApiErrorResponse.java
similarity index 52%
rename from record-api-web/src/main/java/eu/europeana/api/record/model/ApiErrorResponse.java
rename to record-api-common/src/main/java/eu/europeana/api/error/EuropeanaApiErrorResponse.java
index 42879be..4215cf6 100644
--- a/record-api-web/src/main/java/eu/europeana/api/record/model/ApiErrorResponse.java
+++ b/record-api-common/src/main/java/eu/europeana/api/error/EuropeanaApiErrorResponse.java
@@ -1,40 +1,43 @@
-package eu.europeana.api.record.model;
+package eu.europeana.api.error;
-import com.fasterxml.jackson.annotation.*;
-import eu.europeana.api.commons.error.ResponseUtils;
-import jakarta.servlet.http.HttpServletRequest;
-import org.springframework.util.StringUtils;
+import com.fasterxml.jackson.annotation.JsonFormat;
+import com.fasterxml.jackson.annotation.JsonGetter;
+import com.fasterxml.jackson.annotation.JsonInclude;
+import com.fasterxml.jackson.annotation.JsonPropertyOrder;
-import java.time.OffsetDateTime;
import java.util.List;
+import java.time.OffsetDateTime;
-@JsonPropertyOrder({"success", "status", "error", "message", "timestamp", "path"})
+import jakarta.servlet.http.HttpServletRequest;
+import org.springframework.util.StringUtils;
+import static eu.europeana.api.error.EuropeanaErrorConstants.*;
+
+/**
+ * This class contains fields to be returned by APIs when an error occurs within the application.
+ *
+ */
+@JsonPropertyOrder({CONTEXT, TYPE, SUCCESS, STATUS, ERROR, MESSAGE, SEE_ALSO, TIMESTAMP, PATH})
@JsonInclude(JsonInclude.Include.NON_EMPTY)
-public class ApiErrorResponse {
+public class EuropeanaApiErrorResponse {
- @JsonProperty("success")
+ private final String context = ERROR_CONTEXT;
+ private final String type= ERROR_TYPE;
private final boolean success = false;
-
- @JsonProperty("status")
private final int status;
-
- @JsonProperty("error")
private final String error;
-
- @JsonProperty("trace")
- private final String trace;
-
- @JsonProperty("message")
private final String message;
+ private final String seeAlso = SEE_ALSO_VALUE;
- @JsonFormat(
- pattern = "yyyy-MM-dd'T'HH:mm:ss'Z'"
- )
+ @JsonFormat(pattern="yyyy-MM-dd'T'HH:mm:ss'Z'")
private final OffsetDateTime timestamp = OffsetDateTime.now();
+
+ private final String trace;
+
private final String path;
+
private final String code;
- private ApiErrorResponse(int status, String error, String message, String trace, String path, String code) {
+ private EuropeanaApiErrorResponse(int status, String error, String message, String trace, String path, String code) {
this.status = status;
this.error = error;
this.message = message;
@@ -44,35 +47,48 @@ private ApiErrorResponse(int status, String error, String message, String trace,
}
public String getError() {
- return this.error;
+ return error;
}
public boolean isSuccess() {
- return false;
+ return success;
}
public int getStatus() {
- return this.status;
+ return status;
}
public String getMessage() {
- return this.message;
+ return message;
}
public OffsetDateTime getTimestamp() {
- return this.timestamp;
+ return timestamp;
}
- public String getPath() {
- return this.path;
+ public String getTrace() {
+ return trace;
}
- public String getTrace() {
- return this.trace;
+ public String getPath() {
+ return path;
}
public String getCode() {
- return this.code;
+ return code;
+ }
+
+ @JsonGetter(CONTEXT)
+ public String getContext() {
+ return context;
+ }
+
+ public String getType() {
+ return type;
+ }
+
+ public String getSeeAlso() {
+ return seeAlso;
}
public static class Builder {
@@ -84,46 +100,41 @@ public static class Builder {
private String code;
public Builder(HttpServletRequest httpRequest, Exception e, boolean stacktraceEnabled) {
- this.path = getRequestPath(httpRequest);
+ this.path = ResponseUtils.getRequestPath(httpRequest);
boolean includeErrorStack = false;
- String profileParamString = httpRequest.getParameter("profile");
+ String profileParamString = httpRequest.getParameter(QUERY_PARAM_PROFILE);
+ // check if profile contains debug
if (StringUtils.hasLength(profileParamString)) {
- includeErrorStack = List.of(profileParamString.split(",")).contains("debug");
+ includeErrorStack = List.of(profileParamString.split(QUERY_PARAM_PROFILE_SEPARATOR))
+ .contains(PROFILE_DEBUG);
}
-
if (stacktraceEnabled && includeErrorStack) {
this.trace = ResponseUtils.getExceptionStackTrace(e);
}
-
}
- public ApiErrorResponse.Builder setStatus(int status) {
+ public Builder setStatus(int status) {
this.status = status;
return this;
}
- public ApiErrorResponse.Builder setMessage(String message) {
+ public Builder setMessage(String message) {
this.message = message;
return this;
}
- public ApiErrorResponse.Builder setError(String error) {
+ public Builder setError(String error) {
this.error = error;
return this;
}
- public ApiErrorResponse.Builder setCode(String code) {
+ public Builder setCode(String code) {
this.code = code;
return this;
}
- public ApiErrorResponse build() {
- return new ApiErrorResponse(this.status, this.error, this.message, this.trace, this.path, this.code);
+ public EuropeanaApiErrorResponse build() {
+ return new EuropeanaApiErrorResponse(status, error, message, trace, path, code);
}
}
-
- public static String getRequestPath(HttpServletRequest httpRequest) {
- return httpRequest.getQueryString() == null ? String.valueOf(httpRequest.getRequestURL()) : String.valueOf(httpRequest.getRequestURL().append("?").append(httpRequest.getQueryString()));
- }
}
-
diff --git a/record-api-common/src/main/java/eu/europeana/api/error/EuropeanaApiException.java b/record-api-common/src/main/java/eu/europeana/api/error/EuropeanaApiException.java
new file mode 100644
index 0000000..f59a918
--- /dev/null
+++ b/record-api-common/src/main/java/eu/europeana/api/error/EuropeanaApiException.java
@@ -0,0 +1,98 @@
+package eu.europeana.api.error;
+
+import org.springframework.http.HttpStatus;
+
+/**
+ * Base error class for this application. All other application errors should extend this class
+ */
+public class EuropeanaApiException extends Exception {
+
+ /**
+ *
+ */
+ private static final long serialVersionUID = -1354471712894853562L;
+ private final String errorCode;
+
+ /**
+ * Initialise a new exception
+ * @param msg error message
+ * @param t root cause exception
+ */
+ public EuropeanaApiException(String msg, Throwable t) {
+ this(msg, null, t);
+ }
+
+ /**
+ * Initialise a new exception with error code
+ * @param msg error message
+ * @param errorCode error code (optional)
+ * @param t root cause exception
+ */
+ public EuropeanaApiException(String msg, String errorCode, Throwable t) {
+ super(msg, t);
+ this.errorCode = errorCode;
+ }
+
+ /**
+ * Initialise a new exception for which there is no root cause
+ * @param msg error message
+ */
+ public EuropeanaApiException(String msg) {
+ super(msg);
+ this.errorCode = null;
+ }
+
+ /**
+ * Initialise a new exception with error code for which there is no root cause
+ * @param msg error message
+ * @param errorCode error code (optional)
+ */
+ public EuropeanaApiException(String msg, String errorCode) {
+ super(msg);
+ this.errorCode = errorCode;
+ }
+
+ /**
+ * By default we log all exceptions, but you can override this method and return false if you do not want an error
+ * subclass to log the error
+ * @return boolean indicating whether this type of exception should be logged or not.
+ */
+ public boolean doLog() {
+ return true;
+ }
+
+ /**
+ * By default we log error stacktraces, but you can override this method and return false if you do not want an error
+ * subclass to log the error (e.g. in case of common user errors). Note that this only works if doLog is enabled
+ * as well.
+ * @return boolean indicating whether the stacktrace of the exception should be logged or not.
+ */
+ public boolean doLogStacktrace() {
+ return true;
+ }
+
+ /**
+ * @return the error code that was optionally provided when creating this exception
+ */
+ public String getErrorCode() {
+ return this.errorCode;
+ }
+
+ /**
+ * Indicates whether the error message should be include in responses. This is set to true by default.
+ * @return boolean indicating whether the exception message should be exposed to end users
+ */
+ public boolean doExposeMessage() {
+ return true;
+ }
+
+ /**
+ * Gets the HTTP status for this exception.
+ * By default this returns HttpStatus.INTERNAL_SERVER_ERROR
+ *
+ * @return HTTP status for exception
+ */
+ public HttpStatus getResponseStatus() {
+ return HttpStatus.INTERNAL_SERVER_ERROR;
+ }
+}
diff --git a/record-api-common/src/main/java/eu/europeana/api/error/EuropeanaErrorConstants.java b/record-api-common/src/main/java/eu/europeana/api/error/EuropeanaErrorConstants.java
new file mode 100644
index 0000000..e9a0359
--- /dev/null
+++ b/record-api-common/src/main/java/eu/europeana/api/error/EuropeanaErrorConstants.java
@@ -0,0 +1,27 @@
+package eu.europeana.api.error;
+
+public class EuropeanaErrorConstants {
+
+
+ // error fiels
+ public static final String CONTEXT = "@context";
+ public static final String TYPE = "type";
+ public static final String SUCCESS = "success";
+ public static final String STATUS = "status";
+ public static final String ERROR = "error";
+ public static final String MESSAGE = "message";
+ public static final String SEE_ALSO = "seeAlso";
+ public static final String TIMESTAMP = "timestamp";
+ public static final String PATH = "path";
+
+ public static final String ERROR_TYPE= "ErrorResponse";
+ public static final String ERROR_CONTEXT = "http://www.europeana.eu/schemas/context/api.jsonld";
+ public static final String SEE_ALSO_VALUE = "https://pro.europeana.eu/page/apis";
+
+
+ // other constants
+ public static final String QUERY_PARAM_PROFILE = "profile";
+ public static final String QUERY_PARAM_PROFILE_SEPARATOR = ",";
+ public static final String PROFILE_DEBUG = "debug";
+
+}
diff --git a/record-api-common/src/main/java/eu/europeana/api/error/ResponseUtils.java b/record-api-common/src/main/java/eu/europeana/api/error/ResponseUtils.java
new file mode 100644
index 0000000..71236a2
--- /dev/null
+++ b/record-api-common/src/main/java/eu/europeana/api/error/ResponseUtils.java
@@ -0,0 +1,40 @@
+package eu.europeana.api.error;
+
+import jakarta.servlet.http.HttpServletRequest;
+
+import java.io.PrintWriter;
+import java.io.StringWriter;
+import java.io.Writer;
+
+public class ResponseUtils {
+
+ private ResponseUtils() {
+ // hide implicit public constructor
+ }
+
+ /**
+ * Gets a String representation of an Exception's stacktrace
+ *
+ * @param throwable Throwable instance
+ * @return String representation of stacktrace
+ */
+ public static String getExceptionStackTrace(Throwable throwable) {
+ Writer result = new StringWriter();
+ PrintWriter printWriter = new PrintWriter(result);
+ throwable.printStackTrace(printWriter);
+ return result.toString();
+ }
+
+
+ /**
+ * Gets the URI path in the request, appending any query parameters
+ *
+ * @param httpRequest Http request
+ * @return String containing request URI and query parameterss
+ */
+ public static String getRequestPath(HttpServletRequest httpRequest) {
+ return
+ httpRequest.getQueryString() == null ? String.valueOf(httpRequest.getRequestURL()) :
+ String.valueOf(httpRequest.getRequestURL().append("?").append(httpRequest.getQueryString()));
+ }
+}
diff --git a/record-api-common/src/main/java/eu/europeana/api/format/RdfFormat.java b/record-api-common/src/main/java/eu/europeana/api/format/RdfFormat.java
new file mode 100644
index 0000000..ff5027b
--- /dev/null
+++ b/record-api-common/src/main/java/eu/europeana/api/format/RdfFormat.java
@@ -0,0 +1,63 @@
+package eu.europeana.api.format;
+
+public enum RdfFormat {
+ JSONLD("jsonld", "json", "application/ld+json", "application/json"),
+ XML("rdf", "xml", "application/rdf+xml", "application/xml", "text/xml", "rdf/xml"),
+ TURTLE("ttl", null, "text/turtle", "application/turtle", "application/x-turtle"),
+ N3("n3", null, "text/n3", "text/rdf+n3", "application/n3"),
+ NT("nt", null, "application/n-triples", "application/ntriples", "text/nt");
+
+ private String extension;
+ private String alternative;
+ private String[] mediaTypes;
+
+ RdfFormat(String extension, String alternative, String... mediaTypes) {
+ this.extension = extension;
+ this.alternative = alternative;
+ this.mediaTypes = mediaTypes;
+ }
+
+ public static RdfFormat getFormatByExtension(String extension) {
+ for (RdfFormat format : RdfFormat.values()) {
+ if (format.acceptsExtension(extension)) {
+ return format;
+ }
+ }
+ return null;
+ }
+
+ public static RdfFormat getFormatByMediaType(String mediaType) {
+ for (RdfFormat format : RdfFormat.values()) {
+ if (format.acceptsMediaType(mediaType)) {
+ return format;
+ }
+ }
+ return null;
+ }
+
+ public String getExtension() {
+ return extension;
+ }
+
+ public String getAlternative() {
+ return alternative;
+ }
+
+ public String getMediaType() {
+ return mediaTypes[0];
+ }
+
+ public boolean acceptsExtension(String extension) {
+ return (this.extension.equals(extension)
+ || (this.alternative != null && this.alternative.equals(extension)));
+ }
+
+ public boolean acceptsMediaType(String mediaType) {
+ for (String mType : mediaTypes) {
+ if (mType.equals(mediaType)) {
+ return true;
+ }
+ }
+ return false;
+ }
+}
\ No newline at end of file
diff --git a/record-api-common/src/main/java/eu/europeana/api/model/MediaType.java b/record-api-common/src/main/java/eu/europeana/api/model/MediaType.java
new file mode 100644
index 0000000..e2b16e2
--- /dev/null
+++ b/record-api-common/src/main/java/eu/europeana/api/model/MediaType.java
@@ -0,0 +1,63 @@
+package eu.europeana.api.model;
+
+import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty;
+import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlRootElement;
+
+/**
+ * @author srishti singh
+ * @since 19 October 2023
+ */
+@JacksonXmlRootElement(localName = "format")
+public class MediaType {
+
+ private static final String BROWSER = "Browser";
+ private static final String RENDERED = "Rendered";
+ private static final String EU_SCREEN = "EUScreen";
+
+ public static final String VIDEO = "Video";
+ public static final String SOUND = "Sound";
+
+ @JacksonXmlProperty(localName = "mediaType", isAttribute = true)
+ private String mimeType;
+
+ @JacksonXmlProperty(isAttribute = true)
+ private String label;
+
+ @JacksonXmlProperty(isAttribute = true)
+ private String type;
+
+ @JacksonXmlProperty(isAttribute = true)
+ private String support;
+
+ public String getMimeType() {
+ return mimeType;
+ }
+
+ public String getLabel() {
+ return label;
+ }
+
+ public String getType() {
+ return type;
+ }
+
+ public String getSupport() {
+ return support;
+ }
+
+ public boolean isRendered() {
+ return RENDERED.equals(getSupport());
+ }
+
+ public boolean isBrowserSupported() {
+ return BROWSER.equals(getSupport());
+ }
+
+ public boolean isVideoOrSound() {
+ return ( VIDEO.equals(getType()) || SOUND.equals(getType()) ) ;
+ }
+
+ public boolean isEuScreen() {
+ return EU_SCREEN.equals(getSupport());
+ }
+}
diff --git a/record-api-common/src/main/java/eu/europeana/api/model/MediaTypes.java b/record-api-common/src/main/java/eu/europeana/api/model/MediaTypes.java
new file mode 100644
index 0000000..1af24ca
--- /dev/null
+++ b/record-api-common/src/main/java/eu/europeana/api/model/MediaTypes.java
@@ -0,0 +1,62 @@
+package eu.europeana.api.model;
+
+import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlElementWrapper;
+import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty;
+import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlRootElement;
+import org.apache.commons.lang3.StringUtils;
+
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Optional;
+
+/**
+ * @author srishti singh
+ * @since 19 October 2023
+ */
+@JacksonXmlRootElement(localName = "config")
+public class MediaTypes {
+
+ @JacksonXmlElementWrapper(useWrapping = false)
+ @JacksonXmlProperty(localName = "format")
+ public List mediaTypeCategories;
+
+ private Map map = new HashMap<>();
+
+ /**
+ * Map contains all the suppoerted media types except EU Screen entries
+ * @return
+ */
+ public Map getMap() {
+ return this.map;
+ }
+
+
+ /**
+ * Checks if a media Type is configured for the given mime Type
+ *
+ * @param mimeType mime type to match
+ * @return true if a Media Type match is configured, false otherwise.
+ */
+ public boolean hasMediaType(String mimeType) {
+ return map.containsKey(mimeType);
+ }
+
+ /**
+ * Gets the configured media Type for the given entity mime type
+ *
+ * @param mimetype entity ID
+ * @return Matching media Type, or empty Optional if none found
+ */
+ public Optional getMediaType(String mimetype) {
+ if (StringUtils.isNotEmpty(mimetype)) {
+ return Optional.ofNullable(map.get(mimetype));
+ }
+ return Optional.empty();
+ }
+
+ public Optional getEUScreenType(String edmType) {
+ return mediaTypeCategories.stream().filter(s -> s.isEuScreen() && s.getType().equalsIgnoreCase(edmType)).findFirst();
+ }
+
+}
\ No newline at end of file
diff --git a/record-api-common/src/main/java/eu/europeana/api/service/AbstractRequestPathMethodService.java b/record-api-common/src/main/java/eu/europeana/api/service/AbstractRequestPathMethodService.java
new file mode 100644
index 0000000..b898dd0
--- /dev/null
+++ b/record-api-common/src/main/java/eu/europeana/api/service/AbstractRequestPathMethodService.java
@@ -0,0 +1,98 @@
+package eu.europeana.api.service;
+
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Optional;
+import java.util.Set;
+import java.util.stream.Collectors;
+
+import jakarta.servlet.http.HttpServletRequest;
+import org.springframework.beans.factory.InitializingBean;
+import org.springframework.web.context.WebApplicationContext;
+import org.springframework.web.method.HandlerMethod;
+import org.springframework.web.servlet.HandlerMapping;
+import org.springframework.web.servlet.mvc.condition.PatternsRequestCondition;
+import org.springframework.web.servlet.mvc.method.RequestMappingInfo;
+import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping;
+
+/**
+ * This class collates all the HTTP methods that are implemented for all unique request
+ * patterns within a Spring Boot application.
+ *
+ * This is useful for populating the HTTP Allow header, when generating API responses.
+ *
+ * To use:
+ * - extend this class within your project;
+ * - instantiate the subclass with the Spring WebApplicationContext;
+ * - pass the instance as an argument to BaseRestController.createAllowHeader()
+ * */
+public abstract class AbstractRequestPathMethodService implements InitializingBean {
+ /**
+ * Map request urls to Http request methods (implemented across the application) with the url
+ * pattern.
+ */
+ private final Map> requestPathMethodMap = new HashMap<>();
+
+ protected final WebApplicationContext applicationContext;
+
+ protected AbstractRequestPathMethodService(
+ WebApplicationContext applicationContext) {
+ this.applicationContext = applicationContext;
+ }
+
+ /** Populate request url pattern - request methods map */
+ @Override
+ public void afterPropertiesSet() {
+ RequestMappingHandlerMapping mapping =
+ applicationContext.getBean(RequestMappingHandlerMapping.class);
+ Map handlerMethods = mapping.getHandlerMethods();
+
+ for (RequestMappingInfo info : handlerMethods.keySet()) {
+ PatternsRequestCondition p = info.getPatternsCondition();
+
+ // get all request methods for this pattern
+ final Set requestMethods =
+ info.getMethodsCondition().getMethods().stream()
+ .map(Enum::toString)
+ .collect(Collectors.toSet());
+
+ for (String url : p.getPatterns()) {
+ addToMap(requestPathMethodMap, url, requestMethods);
+ }
+ }
+ }
+
+ /**
+ * Gets request methods that are implemented across the application for this request's URL
+ * pattern. The return value from this method is used when setting the Allow header in API
+ * responses.
+ *
+ * @param request {@link HttpServletRequest} instance
+ * @return Optional containing matching request methods, or empty optional if no match could be
+ * determined.
+ */
+ public Optional getMethodsForRequestPattern(HttpServletRequest request) {
+ Object patternAttribute = request.getAttribute(HandlerMapping.BEST_MATCHING_PATTERN_ATTRIBUTE);
+
+ if (patternAttribute == null) {
+ return Optional.empty();
+ }
+
+ Set requestMethods = requestPathMethodMap.get(patternAttribute.toString());
+ String methods = String.join(",", requestMethods);
+ return Optional.of(methods);
+ }
+
+ /** This method adds url patterns and their matching request methods to the map. */
+ private void addToMap(
+ Map> map, String urlPattern, Set requestMethods) {
+ if (!map.containsKey(urlPattern)) {
+ map.put(urlPattern, requestMethods);
+ return;
+ }
+
+ // Each pattern can be used across multiple request handlers, so we append here.
+ Set existing = map.get(urlPattern);
+ existing.addAll(requestMethods);
+ }
+}
diff --git a/record-api-common/src/main/java/eu/europeana/api/service/EuropeanaGlobalExceptionHandler.java b/record-api-common/src/main/java/eu/europeana/api/service/EuropeanaGlobalExceptionHandler.java
new file mode 100644
index 0000000..8f60634
--- /dev/null
+++ b/record-api-common/src/main/java/eu/europeana/api/service/EuropeanaGlobalExceptionHandler.java
@@ -0,0 +1,243 @@
+package eu.europeana.api.service;
+
+import java.time.LocalDate;
+import java.time.LocalDateTime;
+import java.util.List;
+import java.util.Set;
+
+import eu.europeana.api.error.EuropeanaApiErrorResponse;
+import eu.europeana.api.error.EuropeanaApiException;
+import jakarta.servlet.http.HttpServletRequest;
+import jakarta.validation.ConstraintViolationException;
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.Logger;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.boot.autoconfigure.web.ErrorProperties;
+import org.springframework.http.HttpHeaders;
+import org.springframework.http.HttpMethod;
+import org.springframework.http.HttpStatus;
+import org.springframework.http.MediaType;
+import org.springframework.http.ResponseEntity;
+import org.springframework.stereotype.Component;
+import org.springframework.validation.BindingResult;
+import org.springframework.validation.FieldError;
+import org.springframework.web.HttpMediaTypeNotAcceptableException;
+import org.springframework.web.HttpRequestMethodNotSupportedException;
+import org.springframework.web.bind.MethodArgumentNotValidException;
+import org.springframework.web.bind.MissingServletRequestParameterException;
+import org.springframework.web.bind.annotation.ExceptionHandler;
+
+
+/**
+ * Global exception handler that catches all errors and logs the interesting ones
+ * To use this, create a new class in your application that extends this class and add the @ControllerAdvice annotation
+ * to it.
+ */
+@Component
+public class EuropeanaGlobalExceptionHandler {
+
+ @Value("${server.error.include-stacktrace:ON_PARAM}")
+ private ErrorProperties.IncludeStacktrace includeStacktraceConfig;
+
+ private static final Logger LOG = LogManager.getLogger(EuropeanaGlobalExceptionHandler.class);
+
+ protected AbstractRequestPathMethodService requestPathMethodService;
+
+ /**
+ * Checks if {@link EuropeanaApiException} instances should be logged or not
+ *
+ * @param e exception
+ */
+ protected void logException(EuropeanaApiException e) {
+ if (e.doLog()) {
+ if (e.doLogStacktrace()) {
+ LOG.error("Caught exception", e);
+ } else {
+ LOG.error("Caught exception: {}", e.getMessage());
+ }
+ }
+ }
+
+ /**
+ * Checks whether stacktrace for exceptions should be included in responses
+ * @return true if IncludeStacktrace config is not disabled on server
+ */
+ protected boolean stackTraceEnabled(){
+ return includeStacktraceConfig != ErrorProperties.IncludeStacktrace.NEVER;
+ }
+
+ /**
+ * Default handler for EuropeanaApiException types
+ *
+ * @param e caught exception
+ */
+ @ExceptionHandler
+ public ResponseEntity handleEuropeanaBaseException(EuropeanaApiException e, HttpServletRequest httpRequest) {
+ logException(e);
+ EuropeanaApiErrorResponse response = new EuropeanaApiErrorResponse.Builder(httpRequest, e, stackTraceEnabled())
+ .setStatus(e.getResponseStatus().value())
+ .setError(e.getResponseStatus().getReasonPhrase())
+ .setMessage(e.doExposeMessage() ? e.getMessage() : null)
+ // code only included in JSON if a value is set in exception
+ .setCode(e.getErrorCode())
+ .build();
+
+ return ResponseEntity
+ .status(e.getResponseStatus())
+ .headers(createHttpHeaders(httpRequest))
+ .body(response);
+ }
+
+ /**
+ * Default handler for all other exception types
+ *
+ * @param e caught exception
+ */
+ @ExceptionHandler
+ public ResponseEntity handleOtherExceptionTypes(Exception e, HttpServletRequest httpRequest) {
+ LOG.error("Error: ", e);
+ HttpStatus responseStatus = HttpStatus.INTERNAL_SERVER_ERROR;
+ EuropeanaApiErrorResponse response = new EuropeanaApiErrorResponse.Builder(httpRequest, e, stackTraceEnabled())
+ .setStatus(responseStatus.value())
+ .setError(responseStatus.getReasonPhrase())
+ .build();
+
+ return ResponseEntity
+ .status(responseStatus)
+ .headers(createHttpHeaders(httpRequest))
+ .body(response);
+ }
+
+ /**
+ * Handler for HttpRequestMethodNotSupportedException errors
+ * Make sure we return 405 instead of 500 response when http method is not supported; also include error message
+ */
+ @ExceptionHandler
+ public ResponseEntity handleHttpMethodNotSupportedException(HttpRequestMethodNotSupportedException e, HttpServletRequest httpRequest) {
+ HttpStatus responseStatus = HttpStatus.METHOD_NOT_ALLOWED;
+ EuropeanaApiErrorResponse response = new EuropeanaApiErrorResponse.Builder(httpRequest, e, stackTraceEnabled())
+ .setStatus(responseStatus.value())
+ .setError(responseStatus.getReasonPhrase())
+ .setMessage(e.getMessage())
+ .build();
+
+ Set supportedMethods = e.getSupportedHttpMethods();
+
+ // set Allow header in error response
+ HttpHeaders headers = new HttpHeaders();
+ headers.setContentType(MediaType.APPLICATION_JSON);
+
+ if (supportedMethods != null) {
+ headers.setAllow(supportedMethods);
+ }
+ return new ResponseEntity<>(response, headers, responseStatus);
+ }
+
+
+ /**
+ * Handler for ConstraintValidation errors
+ * Make sure we return 400 instead of 500 response when input validation fails; also include error message
+ */
+ @ExceptionHandler
+ public ResponseEntity handleInputValidationError(ConstraintViolationException e, HttpServletRequest httpRequest) {
+ HttpStatus responseStatus = HttpStatus.BAD_REQUEST;
+ EuropeanaApiErrorResponse response = new EuropeanaApiErrorResponse.Builder(httpRequest, e, stackTraceEnabled())
+ .setStatus(responseStatus.value())
+ .setError(responseStatus.getReasonPhrase())
+ .setMessage(e.getMessage())
+ .build();
+
+ return ResponseEntity
+ .status(responseStatus)
+ .headers(createHttpHeaders(httpRequest))
+ .body(response);
+ }
+
+ /**
+ * MissingServletRequestParameterException thrown when a required parameter is not included in a request.
+ */
+ @ExceptionHandler
+ public ResponseEntity handleInputValidationError(MissingServletRequestParameterException e, HttpServletRequest httpRequest) {
+ HttpStatus responseStatus = HttpStatus.BAD_REQUEST;
+ EuropeanaApiErrorResponse response = (new EuropeanaApiErrorResponse.Builder(httpRequest, e, stackTraceEnabled()))
+ .setStatus(responseStatus.value())
+ .setError(responseStatus.getReasonPhrase())
+ .setMessage(e.getMessage())
+ .build();
+
+ return ResponseEntity
+ .status(responseStatus)
+ .headers(createHttpHeaders(httpRequest))
+ .body(response);
+ }
+
+ /**
+ * Customise the response for {@link org.springframework.web.HttpMediaTypeNotAcceptableException}
+ */
+ @ExceptionHandler(HttpMediaTypeNotAcceptableException.class)
+ public ResponseEntity handleMediaTypeNotAcceptableException(
+ HttpMediaTypeNotAcceptableException e, HttpServletRequest httpRequest) {
+
+ HttpStatus responseStatus = HttpStatus.NOT_ACCEPTABLE;
+ EuropeanaApiErrorResponse response = new EuropeanaApiErrorResponse.Builder(httpRequest, e, stackTraceEnabled())
+ .setStatus(responseStatus.value())
+ .setError(responseStatus.getReasonPhrase())
+ .setMessage("Server could not generate a response that is acceptable by the client")
+ .build();
+
+ return ResponseEntity
+ .status(responseStatus)
+ .headers(createHttpHeaders(httpRequest))
+ .body(response);
+ }
+
+
+ /**
+ * Exception thrown by Spring when RequestBody validation fails.
+ */
+ @ExceptionHandler(MethodArgumentNotValidException.class)
+ public ResponseEntity handleMethodArgNotValidException(MethodArgumentNotValidException e, HttpServletRequest httpRequest) {
+ BindingResult result = e.getBindingResult();
+ String error ="";
+ List fieldErrors = result.getFieldErrors();
+ if(!fieldErrors.isEmpty()) {
+ // just return the first error
+ error = fieldErrors.get(0).getField() + " " + fieldErrors.get(0).getDefaultMessage();
+ }
+
+ HttpStatus responseStatus = HttpStatus.BAD_REQUEST;
+ EuropeanaApiErrorResponse response = new EuropeanaApiErrorResponse.Builder(httpRequest, e, stackTraceEnabled())
+ .setStatus(responseStatus.value())
+ .setMessage("Invalid request body")
+ .setError(error)
+ .build();
+
+ return ResponseEntity
+ .status(responseStatus)
+ .headers(createHttpHeaders(httpRequest))
+ .body(response);
+ }
+
+ protected HttpHeaders createHttpHeaders(HttpServletRequest httpRequest) {
+ HttpHeaders headers = new HttpHeaders();
+ //enforce application/json as content type, it is the only serialization supported for exceptions
+ headers.setContentType(MediaType.APPLICATION_JSON);
+
+ //autogenerate allow header if the service is configured
+ if(getRequestPathMethodService()!=null) {
+ String allowHeaderValue = getRequestPathMethodService().getMethodsForRequestPattern(httpRequest).orElse(httpRequest.getMethod());
+ headers.add(HttpHeaders.ALLOW, allowHeaderValue);
+ }
+ return headers;
+ }
+
+ /**
+ * The bean needs to be defined in the individual APIs
+ *
+ * @return
+ */
+ AbstractRequestPathMethodService getRequestPathMethodService() {
+ return requestPathMethodService;
+ }
+}
+
diff --git a/record-api-common/src/main/resources/mediacategories.xml b/record-api-common/src/main/resources/mediacategories.xml
new file mode 100644
index 0000000..1d5985a
--- /dev/null
+++ b/record-api-common/src/main/resources/mediacategories.xml
@@ -0,0 +1,40 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/record-api-web/pom.xml b/record-api-web/pom.xml
index e78c5d8..a6d8445 100644
--- a/record-api-web/pom.xml
+++ b/record-api-web/pom.xml
@@ -77,6 +77,10 @@
xml-apis
xml-apis
+
+ eu.europeana.api.commons
+ commons-error
+
diff --git a/record-api-web/src/main/java/eu/europeana/api/record/exception/GlobalExceptionHandler.java b/record-api-web/src/main/java/eu/europeana/api/record/exception/GlobalExceptionHandler.java
index 6533c0d..4d84f10 100644
--- a/record-api-web/src/main/java/eu/europeana/api/record/exception/GlobalExceptionHandler.java
+++ b/record-api-web/src/main/java/eu/europeana/api/record/exception/GlobalExceptionHandler.java
@@ -1,16 +1,6 @@
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.beans.factory.annotation.Value;
-import org.springframework.boot.autoconfigure.web.ErrorProperties;
-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 eu.europeana.api.service.EuropeanaGlobalExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;
/**
@@ -20,49 +10,6 @@
* Created on 24-08-2023
*/
@RestControllerAdvice
-@ResponseBody
-class GlobalExceptionHandler {
-
- private static final Logger LOG = LogManager.getLogger(GlobalExceptionHandler.class);
-
- @Value("${server.error.include-stacktrace:ON_PARAM}")
- private ErrorProperties.IncludeStacktrace includeStacktraceConfig;
-
- // 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());
- }
- }
-
- }
-
- protected boolean stackTraceEnabled() {
- return this.includeStacktraceConfig != ErrorProperties.IncludeStacktrace.NEVER;
- }
-
-
- @ExceptionHandler
- public ResponseEntity handleBaseException(RecordApiException e, HttpServletRequest httpRequest) {
- this.logException(e);
- ApiErrorResponse response = (new ApiErrorResponse.Builder(httpRequest, e, this.stackTraceEnabled())).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;
- }
+class GlobalExceptionHandler extends EuropeanaGlobalExceptionHandler {
}
\ No newline at end of file
diff --git a/record-api-web/src/main/java/eu/europeana/api/record/exception/HttpBadRequestException.java b/record-api-web/src/main/java/eu/europeana/api/record/exception/HttpBadRequestException.java
index 0247d5b..137fbdb 100644
--- a/record-api-web/src/main/java/eu/europeana/api/record/exception/HttpBadRequestException.java
+++ b/record-api-web/src/main/java/eu/europeana/api/record/exception/HttpBadRequestException.java
@@ -1,9 +1,10 @@
package eu.europeana.api.record.exception;
+import eu.europeana.api.error.EuropeanaApiException;
import org.springframework.http.HttpStatus;
/** Exception thrown when an error occurs due to bad user input. */
-public class HttpBadRequestException extends RecordApiException {
+public class HttpBadRequestException extends EuropeanaApiException {
public HttpBadRequestException(String msg) {
super(msg);
diff --git a/record-api-web/src/main/java/eu/europeana/api/record/exception/RecordAlreadyExistsException.java b/record-api-web/src/main/java/eu/europeana/api/record/exception/RecordAlreadyExistsException.java
index fdd69fb..71377ae 100644
--- a/record-api-web/src/main/java/eu/europeana/api/record/exception/RecordAlreadyExistsException.java
+++ b/record-api-web/src/main/java/eu/europeana/api/record/exception/RecordAlreadyExistsException.java
@@ -1,9 +1,10 @@
package eu.europeana.api.record.exception;
+import eu.europeana.api.error.EuropeanaApiException;
import org.springframework.http.HttpStatus;
/** Exception thrown when a record already exists in the DB. */
-public class RecordAlreadyExistsException extends RecordApiException {
+public class RecordAlreadyExistsException extends EuropeanaApiException {
public RecordAlreadyExistsException(String about) {
super("Record already exists for '" + about);
diff --git a/record-api-web/src/main/java/eu/europeana/api/record/exception/RecordApiException.java b/record-api-web/src/main/java/eu/europeana/api/record/exception/RecordApiException.java
deleted file mode 100644
index a752066..0000000
--- a/record-api-web/src/main/java/eu/europeana/api/record/exception/RecordApiException.java
+++ /dev/null
@@ -1,48 +0,0 @@
-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;
- }
-}
diff --git a/record-api-web/src/main/java/eu/europeana/api/record/exception/RecordDoesNotExistsException.java b/record-api-web/src/main/java/eu/europeana/api/record/exception/RecordDoesNotExistsException.java
index 0819c0c..cfe89b4 100644
--- a/record-api-web/src/main/java/eu/europeana/api/record/exception/RecordDoesNotExistsException.java
+++ b/record-api-web/src/main/java/eu/europeana/api/record/exception/RecordDoesNotExistsException.java
@@ -1,8 +1,9 @@
package eu.europeana.api.record.exception;
+import eu.europeana.api.error.EuropeanaApiException;
import org.springframework.http.HttpStatus;
-public class RecordDoesNotExistsException extends RecordApiException {
+public class RecordDoesNotExistsException extends EuropeanaApiException {
public RecordDoesNotExistsException(String about) {
super("Record does not exists for '" + about);
diff --git a/record-api-web/src/main/java/eu/europeana/api/record/web/RecordController.java b/record-api-web/src/main/java/eu/europeana/api/record/web/RecordController.java
index 62d8b5e..7912c9d 100644
--- a/record-api-web/src/main/java/eu/europeana/api/record/web/RecordController.java
+++ b/record-api-web/src/main/java/eu/europeana/api/record/web/RecordController.java
@@ -1,7 +1,7 @@
package eu.europeana.api.record.web;
import eu.europeana.api.commons.web.http.HttpHeaders;
-import eu.europeana.api.record.exception.RecordApiException;
+import eu.europeana.api.error.EuropeanaApiException;
import eu.europeana.api.record.exception.RecordDoesNotExistsException;
import eu.europeana.api.record.model.ProvidedCHO;
import eu.europeana.api.record.serialization.JsonLdSerializer;