Skip to content

Commit

Permalink
af
Browse files Browse the repository at this point in the history
  • Loading branch information
shubhamv108 committed Jan 8, 2024
1 parent cb1c09d commit e7380c1
Show file tree
Hide file tree
Showing 20 changed files with 371 additions and 58 deletions.
52 changes: 0 additions & 52 deletions .github/workflows/gradle.yml

This file was deleted.

File renamed without changes.
4 changes: 1 addition & 3 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -71,12 +71,10 @@ dependencies {
'org.springframework.boot:spring-boot-starter-web',
'org.springframework.kafka:spring-kafka',

'org.springframework.boot:spring-boot-starter-oauth2-resource-server',
'org.springframework.boot:spring-boot-starter-security',
// 'org.springframework.boot:spring-boot-starter-oauth2-resource-server',
'org.springframework.boot:spring-boot-starter-oauth2-resource-server',
'software.amazon.awssdk:s3:2.22.1',
'org.apache.commons:commons-lang3:3.14.0',
'org.springframework.boot:spring-boot-starter-security',
'org.springframework.boot:spring-boot-starter-thymeleaf',
'org.springdoc:springdoc-openapi-starter-webmvc-ui:2.2.0',
'org.springframework.boot:spring-boot-starter-validation',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,9 @@
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.domain.AuditorAware;
import org.springframework.data.jpa.repository.config.EnableJpaAuditing;

@EnableJpaAuditing
@Configuration
public class PersistenceConfig {

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ public ResponseEntity<?> handleAccessDeniedException(final Exception exception,
return ResponseUtils.getErrorResponseEntity(HttpStatus.FORBIDDEN.value(), "Access denied");
}

@ExceptionHandler({ SQLIntegrityConstraintViolationException.class })
@ExceptionHandler({ SQLIntegrityConstraintViolationException.class, DataIntegrityViolationException.class })
public ResponseEntity<?> handleSQLIntegrityConstraintViolationException(
final SQLIntegrityConstraintViolationException exception, final WebRequest request) {
return ResponseUtils.getErrorResponseEntity(HttpStatus.BAD_REQUEST.value(), exception.getMessage());
Expand Down Expand Up @@ -55,7 +55,7 @@ public ResponseEntity<?> handleBlobStoreException(final InvalidRequestException
return ResponseUtils.getErrorResponseEntity(HttpStatus.BAD_REQUEST.value(), exception.getOriginalErrors());
}

@ExceptionHandler({ PropertyValueException.class, DataIntegrityViolationException.class, Exception.class })
@ExceptionHandler({ PropertyValueException.class, Exception.class })
public ResponseEntity<?> handleServerExceptions(final Exception exception, final WebRequest request) {
log.error("", exception);
Sentry.captureException(exception);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,11 @@
import code.shubham.core.iammodels.GetOrCreateUser;
import code.shubham.core.iammodels.GetUserResponse;
import code.shubham.core.iammodels.UserDTO;
import jakarta.servlet.*;
import jakarta.servlet.Filter;
import jakarta.servlet.FilterChain;
import jakarta.servlet.ServletException;
import jakarta.servlet.ServletRequest;
import jakarta.servlet.ServletResponse;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.annotation.Order;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,17 @@
import code.shubham.commons.utils.ResponseUtils;
import code.shubham.core.iam.services.UserService;
import code.shubham.core.iammodels.GetOrCreateUser;
import io.swagger.v3.oas.annotations.security.SecurityRequirement;
import io.swagger.v3.oas.annotations.tags.Tag;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;

@RestController
@RequestMapping("/v1/users")
@SecurityRequirement(name = "BearerAuth")
@Tag(name = "User")
public class UserController {

private final UserService userService;
Expand Down
17 changes: 17 additions & 0 deletions src/main/java/code/shubham/core/lock/Constants.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package code.shubham.core.lock;

public interface Constants {

interface Queries {

String INSERT = "INSERT INTO locks VALUES (UUID(), ?, ?, ?, TIMESTAMPADD(SECOND, ?, NOW()))";

String LOCK = "UPDATE locks SET owner = ?, expiry_at = TIMESTAMPADD(SECOND, ?, NOW()), version = version + 1 WHERE name = ? AND version = ? AND (expiry_at is null OR expiry_at < NOW())";

String RENEW = "UPDATE locks SET expiry_at = TIMESTAMPADD(SECOND, ?, expiry_at), version = version + 1 WHERE name = ? AND owner = ? AND version = ? AND now() < expiry_at";

String UNLOCK = "UPDATE locks SET owner = null, expiry_at = now(), version = version + 1 WHERE name = ? AND owner = ? AND version = ?";

}

}
40 changes: 40 additions & 0 deletions src/main/java/code/shubham/core/lock/services/LockService.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
package code.shubham.core.lock.services;

import code.shubham.core.lock.web.v1.dao.entites.Lock;
import code.shubham.core.lock.web.v1.dao.repositories.LockRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.util.Optional;

@Service
public class LockService {

private final LockRepository repository;

@Autowired
public LockService(final LockRepository repository) {
this.repository = repository;
}

public boolean lock(final String name, final int previousVersion, final String owner,
final long timeToLiveInSeconds) {
if (previousVersion == 0)
return this.repository.insert(name, 1, owner, timeToLiveInSeconds) == 1;
else
return this.repository.lock(owner, timeToLiveInSeconds, name, previousVersion) == 1;
}

public boolean renew(final String name, final int version, final String owner, final long timeToLiveInSeconds) {
return this.repository.renew(timeToLiveInSeconds, name, owner, version) == 1;
}

public boolean unlock(final String name, final String owner, final int version) {
return this.repository.unlock(name, owner, version) == 1;
}

public Optional<Lock> fetchByName(final String name) {
return this.repository.findByName(name);
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
package code.shubham.core.lock.web.v1.controllers;

import code.shubham.commons.exceptions.InvalidRequestException;
import code.shubham.commons.utils.ResponseUtils;
import code.shubham.commons.utils.StringUtils;
import code.shubham.core.lock.services.LockService;
import code.shubham.core.lock.web.v1.validators.LockRequestValidator;
import code.shubham.core.lock.web.v1.validators.UnlockRequestValidator;
import code.shubham.core.lockmodels.LockDTO;
import code.shubham.core.lockmodels.LockRequestDTO;
import code.shubham.core.lockmodels.UnlockRequestDTO;
import io.swagger.v3.oas.annotations.security.SecurityRequirement;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.validation.constraints.NotEmpty;
import jakarta.validation.constraints.NotNull;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PatchMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PutMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequestMapping("/v1/locks")
@SecurityRequirement(name = "BearerAuth")
@Tag(name = "Lock")
public class LockController {

private final LockService service;

@Autowired
public LockController(final LockService service) {
this.service = service;
}

@GetMapping("/{name}")
public ResponseEntity<?> getByName(@PathVariable @NotNull @NotEmpty final String name) {
this.validateNameOrThrowException(name);

return ResponseUtils.getDataResponseEntity(HttpStatus.OK.value(),
this.service.fetchByName(name)
.map(lock -> LockDTO.builder().version(lock.getVersion()).build())
.orElseThrow(() -> new InvalidRequestException("name", "no lock for name", name)));

}

@PutMapping("/{name}")
public ResponseEntity<?> lock(@PathVariable @NotNull @NotEmpty final String name,
@RequestBody final LockRequestDTO request) {
this.validateNameOrThrowException(name);
new LockRequestValidator().validateOrThrowException(request);

if (this.service.lock(name, request.getPreviousVersion(), request.getOwner(), request.getTimeToLiveInSeconds()))
return ResponseUtils.getResponseEntity(HttpStatus.OK.value());

return ResponseUtils.getErrorResponseEntity(HttpStatus.LOCKED.value(),
String.format("%s already locked", name));
}

@PatchMapping("/{name}")
public ResponseEntity<?> renew(@PathVariable @NotNull @NotEmpty final String name,
@RequestBody final LockRequestDTO request) {
this.validateNameOrThrowException(name);
new LockRequestValidator().validateOrThrowException(request);

if (this.service.renew(name, request.getPreviousVersion(), request.getOwner(),
request.getTimeToLiveInSeconds()))
return ResponseUtils.getResponseEntity(HttpStatus.OK.value());
return ResponseUtils.getErrorResponseEntity(HttpStatus.NOT_FOUND.value(),
String.format("Lock not found on name: %s for owner: %s with version: %d", name, request.getOwner(),
request.getPreviousVersion()));
}

@DeleteMapping("/{name}")
public ResponseEntity<?> unlock(@PathVariable @NotNull @NotEmpty final String name,
@RequestBody final UnlockRequestDTO request) {
this.validateNameOrThrowException(name);
new UnlockRequestValidator().validateOrThrowException(request);

if (this.service.unlock(name, request.getOwner(), request.getVersion()))
return ResponseUtils.getResponseEntity(HttpStatus.OK.value());
return ResponseUtils.getErrorResponseEntity(HttpStatus.NOT_FOUND.value(),
String.format("Lock not found on name: %s for owner: %s with version: %d", name, request.getOwner(),
request.getVersion()));
}

private void validateNameOrThrowException(final String name) {
if (StringUtils.isEmpty(name))
throw new InvalidRequestException("name", "must not be empty", "name");
}

}
37 changes: 37 additions & 0 deletions src/main/java/code/shubham/core/lock/web/v1/dao/entites/Lock.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
package code.shubham.core.lock.web.v1.dao.entites;

import code.shubham.commons.dao.entities.base.BaseIdEntity;
import com.fasterxml.jackson.annotation.JsonIgnore;
import jakarta.persistence.*;
import lombok.*;
import lombok.experimental.SuperBuilder;
import org.springframework.data.jpa.domain.support.AuditingEntityListener;

import java.util.Date;

@SuperBuilder
@Builder
@Data
@NoArgsConstructor
@AllArgsConstructor
@EqualsAndHashCode(callSuper = true)
@Entity
@Table(name = "locks")
public class Lock extends BaseIdEntity {

@Column(nullable = false, unique = true, length = 64)
private String name;

@Column(length = 64, nullable = false)
private String owner;

@JsonIgnore
@Temporal(TemporalType.TIMESTAMP)
@Column(name = "expiry_at")
private Date expiryAt;

@Builder.Default
@Version
private Integer version = 0;

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
package code.shubham.core.lock.web.v1.dao.repositories;

import code.shubham.core.lock.Constants;
import code.shubham.core.lock.web.v1.dao.entites.Lock;
import jakarta.transaction.Transactional;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Modifying;
import org.springframework.data.jpa.repository.Query;
import org.springframework.stereotype.Repository;

import java.util.Date;
import java.util.Optional;

@Repository
public interface LockRepository extends JpaRepository<Lock, String> {

@Transactional
@Modifying
@Query(nativeQuery = true, value = Constants.Queries.INSERT)
int insert(String name, int version, String owner, long timeToLiveInSeconds);

Optional<Lock> findByName(String name);

@Transactional
@Modifying
@Query(nativeQuery = true, value = Constants.Queries.LOCK)
int lock(String owner, long timeToLiveInSeconds, String name, int previousVersion);

@Transactional
@Modifying
@Query(nativeQuery = true, value = Constants.Queries.RENEW)
int renew(long timeToLiveInSeconds, String name, String owner, int version);

@Transactional
@Modifying
@Query(nativeQuery = true, value = Constants.Queries.UNLOCK)
int unlock(String name, String owner, int version);

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
package code.shubham.core.lock.web.v1.validators;

import code.shubham.commons.utils.StringUtils;
import code.shubham.commons.validators.AbstractRequestValidator;
import code.shubham.commons.validators.IValidator;
import code.shubham.core.lockmodels.LockRequestDTO;

import java.util.Objects;

public class LockRequestValidator extends AbstractRequestValidator<LockRequestDTO> {

@Override
public IValidator<LockRequestDTO> validate(final LockRequestDTO request) {
super.validate(request);

if (StringUtils.isEmpty(request.getOwner()))
this.putMessage("owner", MUST_NOT_BE_EMPTY, "version");

if (Objects.isNull(request.getPreviousVersion()))
this.putMessage("previousVersion", MUST_NOT_BE_EMPTY, "previousVersion");

if (Objects.isNull(request.getTimeToLiveInSeconds()))
this.putMessage("timeToLiveInSeconds", MUST_NOT_BE_EMPTY, "timeToLiveInSeconds");

return this;
}

}
Loading

0 comments on commit e7380c1

Please sign in to comment.