Skip to content

Commit

Permalink
MODELINKS-2 Create PUT endpoint (#2)
Browse files Browse the repository at this point in the history
* MODELINKS-2 Create PUT endpoint

Co-authored-by: Shans Kaluhin <[email protected]>
  • Loading branch information
psmagin and Shans-Kaluhin authored Sep 6, 2022
1 parent 8b7a026 commit 3367001
Show file tree
Hide file tree
Showing 25 changed files with 1,352 additions and 10 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ target/
!**/src/test/**/target/
bin/
.checkstyle
.jpb

### STS ###
.apt_generated
Expand Down
14 changes: 12 additions & 2 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.7.0</version>
<version>2.7.3</version>
<relativePath /> <!-- lookup parent from repository -->
</parent>

Expand All @@ -20,6 +20,10 @@
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<mod-entities-links.yaml.file>${project.basedir}/src/main/resources/swagger.api/mod-entities-links.yaml</mod-entities-links.yaml.file>
<sonar.exclusions>
src/main/java/org/folio/entlinks/EntityLinksApplication.java,
src/main/java/org/folio/entlinks/model/**
</sonar.exclusions>

<folio-spring-base.version>4.1.0</folio-spring-base.version>
<mapstruct.version>1.5.2.Final</mapstruct.version>
Expand All @@ -29,6 +33,7 @@

<openapi-generator.version>5.4.0</openapi-generator.version>
<copy-rename-maven-plugin.version>1.0.1</copy-rename-maven-plugin.version>
<hibernate-types.version>2.18.0</hibernate-types.version>
</properties>

<dependencies>
Expand All @@ -55,6 +60,11 @@
<groupId>com.fasterxml.jackson.datatype</groupId>
<artifactId>jackson-datatype-jdk8</artifactId>
</dependency>
<dependency>
<groupId>com.vladmihalcea</groupId>
<artifactId>hibernate-types-55</artifactId>
<version>${hibernate-types.version}</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
Expand Down Expand Up @@ -232,7 +242,7 @@
<supportingFilesToGenerate>ApiUtil.java</supportingFilesToGenerate>
<generateModelDocumentation>true</generateModelDocumentation>
<importMappings>
<importMapping>error=org.folio.tenant.domain.dto.Error</importMapping>
<importMapping>error=org.folio.tenant.domain.dto.Errors</importMapping>
</importMappings>
<configOptions>
<useTags>true</useTags>
Expand Down
133 changes: 133 additions & 0 deletions src/main/java/org/folio/entlinks/controller/ApiErrorHandler.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,133 @@
package org.folio.entlinks.controller;

import static java.util.Collections.emptyList;
import static org.apache.logging.log4j.Level.DEBUG;
import static org.apache.logging.log4j.Level.WARN;
import static org.folio.entlinks.model.type.ErrorCode.UNKNOWN_ERROR;
import static org.folio.entlinks.model.type.ErrorCode.VALIDATION_ERROR;
import static org.springframework.http.HttpStatus.BAD_REQUEST;
import static org.springframework.http.HttpStatus.INTERNAL_SERVER_ERROR;
import static org.springframework.http.HttpStatus.UNPROCESSABLE_ENTITY;

import java.util.List;
import java.util.Optional;
import javax.validation.ConstraintViolationException;
import lombok.extern.log4j.Log4j2;
import org.apache.logging.log4j.Level;
import org.folio.entlinks.exception.RequestBodyValidationException;
import org.folio.entlinks.model.type.ErrorCode;
import org.folio.tenant.domain.dto.Error;
import org.folio.tenant.domain.dto.Errors;
import org.folio.tenant.domain.dto.Parameter;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.http.converter.HttpMessageNotReadableException;
import org.springframework.validation.FieldError;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.MissingServletRequestParameterException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import org.springframework.web.method.annotation.MethodArgumentTypeMismatchException;

@Log4j2
@RestControllerAdvice
public class ApiErrorHandler {

private static ResponseEntity<Errors> buildResponseEntity(Exception e, HttpStatus status, ErrorCode code) {
var errors = new Errors()
.errors(List.of(new Error()
.message(e.getMessage())
.type(e.getClass().getSimpleName())
.code(code.getValue())))
.totalRecords(1);
return buildResponseEntity(errors, status);
}

private static ResponseEntity<Errors> buildResponseEntity(Errors errorResponse, HttpStatus status) {
return ResponseEntity.status(status).body(errorResponse);
}

private static void logException(Level logLevel, Exception e) {
log.log(logLevel, "Handling e", e);
}

private static Errors buildValidationError(Exception e, List<Parameter> parameters) {
var error = new Error()
.type(e.getClass().getSimpleName())
.code(VALIDATION_ERROR.getValue())
.message(e.getMessage())
.parameters(parameters);
return new Errors().errors(List.of(error)).totalRecords(1);
}

@ExceptionHandler(Exception.class)
public ResponseEntity<Errors> handleGlobalExceptions(Exception e) {
logException(WARN, e);
return buildResponseEntity(e, INTERNAL_SERVER_ERROR, UNKNOWN_ERROR);
}

@ExceptionHandler(RequestBodyValidationException.class)
public ResponseEntity<Errors> handleRequestValidationException(RequestBodyValidationException e) {
logException(DEBUG, e);
var errorResponse = buildValidationError(e, e.getInvalidParameters());
return buildResponseEntity(errorResponse, UNPROCESSABLE_ENTITY);
}

@ExceptionHandler(MethodArgumentNotValidException.class)
public ResponseEntity<Errors> handleMethodArgumentNotValidException(MethodArgumentNotValidException e) {
logException(DEBUG, e);
var errors = Optional.of(e.getBindingResult())
.map(org.springframework.validation.Errors::getAllErrors)
.orElse(emptyList())
.stream()
.map(error -> new Error()
.message(error.getDefaultMessage())
.code(VALIDATION_ERROR.getValue())
.type(MethodArgumentNotValidException.class.getSimpleName())
.addParametersItem(new Parameter()
.key(((FieldError) error).getField())
.value(String.valueOf(((FieldError) error).getRejectedValue()))))
.toList();

var errorResponse = new Errors().errors(errors).totalRecords(errors.size());
return buildResponseEntity(errorResponse, UNPROCESSABLE_ENTITY);
}

@ExceptionHandler(ConstraintViolationException.class)
public ResponseEntity<Errors> handleConstraintViolation(ConstraintViolationException e) {
logException(DEBUG, e);
var errors = e.getConstraintViolations().stream()
.map(constraintViolation -> new Error()
.message(String.format("%s %s", constraintViolation.getPropertyPath(), constraintViolation.getMessage()))
.code(VALIDATION_ERROR.getValue())
.type(ConstraintViolationException.class.getSimpleName()))
.toList();

var errorResponse = new Errors().errors(errors).totalRecords(errors.size());
return buildResponseEntity(errorResponse, BAD_REQUEST);
}

@ExceptionHandler({
MethodArgumentTypeMismatchException.class,
MissingServletRequestParameterException.class,
IllegalArgumentException.class
})
public ResponseEntity<Errors> handleValidationException(Exception e) {
logException(DEBUG, e);
return buildResponseEntity(e, BAD_REQUEST, VALIDATION_ERROR);
}

@ExceptionHandler(HttpMessageNotReadableException.class)
public ResponseEntity<Errors> handlerHttpMessageNotReadableException(HttpMessageNotReadableException e) {
return Optional.ofNullable(e.getCause())
.map(Throwable::getCause)
.filter(IllegalArgumentException.class::isInstance)
.map(IllegalArgumentException.class::cast)
.map(this::handleValidationException)
.orElseGet(() -> {
logException(DEBUG, e);
return buildResponseEntity(e, BAD_REQUEST, VALIDATION_ERROR);
});
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
package org.folio.entlinks.controller;

import java.util.UUID;
import lombok.RequiredArgsConstructor;
import org.folio.entlinks.service.InstanceLinkService;
import org.folio.qm.domain.dto.InstanceLinkDtoCollection;
import org.folio.qm.rest.resource.InstanceLinksApi;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequiredArgsConstructor
public class InstanceLinksController implements InstanceLinksApi {

private final InstanceLinkService instanceLinkService;

@Override
public ResponseEntity<Void> updateInstanceLinks(UUID instanceId, InstanceLinkDtoCollection instanceLinkCollection) {
instanceLinkService.updateInstanceLinks(instanceId, instanceLinkCollection);
return ResponseEntity.noContent().build();
}
}
24 changes: 24 additions & 0 deletions src/main/java/org/folio/entlinks/exception/BaseException.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package org.folio.entlinks.exception;

import lombok.Getter;
import org.folio.entlinks.model.type.ErrorCode;

/**
* Base exception class that is used for all exceptional situations
*/
@Getter
public abstract class BaseException extends RuntimeException {

private final ErrorCode errorCode;

/**
* Initialize exception with provided message and error code.
*
* @param message exception message
* @param errorCode exception code {@link ErrorCode}
*/
protected BaseException(String message, ErrorCode errorCode) {
super(message);
this.errorCode = errorCode;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
package org.folio.entlinks.exception;

import java.util.List;
import lombok.Getter;
import org.folio.entlinks.model.type.ErrorCode;
import org.folio.tenant.domain.dto.Parameter;

/**
* Exception for situations when request body is invalid
*/
@Getter
public class RequestBodyValidationException extends BaseException {

private final transient List<Parameter> invalidParameters;

/**
* Initialize exception with provided message, error code and invalid parameters.
*
* @param message exception message
* @param invalidParameters list of invalid parameters {@link Parameter}
*/
public RequestBodyValidationException(String message, List<Parameter> invalidParameters) {
super(message, ErrorCode.VALIDATION_ERROR);
this.invalidParameters = invalidParameters;
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package org.folio.entlinks.model.converter;

import org.folio.entlinks.model.entity.InstanceLink;
import org.folio.qm.domain.dto.InstanceLinkDto;
import org.mapstruct.Mapper;

@Mapper(componentModel = "spring")
public interface InstanceLinkMapper {

InstanceLinkDto convert(InstanceLink source);

InstanceLink convert(InstanceLinkDto source);
}
81 changes: 81 additions & 0 deletions src/main/java/org/folio/entlinks/model/entity/InstanceLink.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
package org.folio.entlinks.model.entity;

import com.vladmihalcea.hibernate.type.array.ListArrayType;
import java.util.List;
import java.util.Objects;
import java.util.UUID;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.Index;
import javax.persistence.Table;
import javax.validation.constraints.NotNull;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
import lombok.ToString;
import org.hibernate.Hibernate;
import org.hibernate.annotations.Type;
import org.hibernate.annotations.TypeDef;

@Getter
@Setter
@ToString
@NoArgsConstructor
@AllArgsConstructor
@Builder
@Entity
@Table(name = "instance_link", indexes = {
@Index(name = "idx_instancelink_authority_id", columnList = "authority_id"),
@Index(name = "idx_instancelink_instance_id", columnList = "instance_id")
})
@TypeDef(name = "list-array", typeClass = ListArrayType.class)
public class InstanceLink {

@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "id", nullable = false)
private Long id;

@NotNull
@Column(name = "authority_id", nullable = false)
private UUID authorityId;

@NotNull
@Column(name = "authority_natural_id", nullable = false, length = 100)
private String authorityNaturalId;

@NotNull
@Column(name = "instance_id", nullable = false)
private UUID instanceId;

@Column(name = "bib_record_tag", length = 3)
private String bibRecordTag;

@Type(type = "list-array")
@Column(name = "bib_record_subfields")
private List<String> bibRecordSubfields;

@Override
public int hashCode() {
return getClass().hashCode();
}

@Override
public boolean equals(Object o) {
if (this == o) { return true; }
if (o == null || Hibernate.getClass(this) != Hibernate.getClass(o)) { return false; }
InstanceLink instanceLink = (InstanceLink) o;
return id != null && Objects.equals(id, instanceLink.id);
}

public boolean isSameLink(InstanceLink link) {
return authorityId.equals(link.authorityId)
&& instanceId.equals(link.instanceId)
&& bibRecordTag.equals(link.bibRecordTag);
}
}
14 changes: 14 additions & 0 deletions src/main/java/org/folio/entlinks/model/type/ErrorCode.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package org.folio.entlinks.model.type;

import lombok.Getter;
import lombok.RequiredArgsConstructor;

@Getter
@RequiredArgsConstructor
public enum ErrorCode {

VALIDATION_ERROR("validation"),
UNKNOWN_ERROR("unknown");

private final String value;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package org.folio.entlinks.repository;

import java.util.List;
import java.util.UUID;
import org.folio.entlinks.model.entity.InstanceLink;
import org.springframework.data.jpa.repository.JpaRepository;

public interface InstanceLinkRepository extends JpaRepository<InstanceLink, Long> {

List<InstanceLink> findByInstanceId(UUID instanceId);

}
Loading

0 comments on commit 3367001

Please sign in to comment.