Skip to content

Commit

Permalink
feat: introduce UserContextData and Auditable
Browse files Browse the repository at this point in the history
feat: CommonConstant
feat: CommonMapper
feat: introduce AbstractAuditableEntity, AbstractBaseEntity interface in common
  • Loading branch information
dano authored and thongdanghoang committed Dec 11, 2024
1 parent 7540621 commit 829e596
Show file tree
Hide file tree
Showing 22 changed files with 295 additions and 186 deletions.
2 changes: 2 additions & 0 deletions sep490-common/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -35,11 +35,13 @@ dependencies {
implementation group: 'org.apache.commons', name: 'commons-compress', version: '1.27.1'
implementation group: 'org.apache.commons', name: 'commons-math3', version: '3.6.1'
implementation group: 'commons-io', name: 'commons-io', version: '2.17.0'
implementation group: 'commons-validator', name: 'commons-validator', version: '1.9.0'

api group: 'org.apache.commons', name: 'commons-lang3', version: '3.17.0'
api group: 'org.apache.commons', name: 'commons-collections4', version: '4.4'
api group: 'org.apache.commons', name: 'commons-text', version: '1.12.0'
api group: 'org.apache.commons', name: 'commons-compress', version: '1.27.1'
api group: 'org.apache.commons', name: 'commons-math3', version: '3.6.1'
api group: 'commons-io', name: 'commons-io', version: '2.17.0'
api group: 'commons-validator', name: 'commons-validator', version: '1.9.0'
}
10 changes: 10 additions & 0 deletions sep490-common/src/main/java/sep490/common/api/AuditableEntity.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package sep490.common.api;

import java.time.LocalDateTime;

public interface AuditableEntity {
String getCreatedBy();
LocalDateTime getCreatedDate();
String getLastModifiedBy();
LocalDateTime getLastModifiedDate();
}
8 changes: 8 additions & 0 deletions sep490-common/src/main/java/sep490/common/api/BaseEntity.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package sep490.common.api;

import java.util.UUID;

public interface BaseEntity {
UUID getId();
int getVersion();
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@

import java.time.LocalDate;

public record DateRange(
public record DateRangeDTO(
@NotNull
LocalDate from,
@NotNull
Expand All @@ -16,11 +16,11 @@ public record DateRange(
DateBoundary toBoundary
) {

public static DateRange of(LocalDate from, LocalDate to, DateBoundary fromBoundary, DateBoundary toBoundary) {
return new DateRange(from, to, fromBoundary, toBoundary);
public static DateRangeDTO of(LocalDate from, LocalDate to, DateBoundary fromBoundary, DateBoundary toBoundary) {
return new DateRangeDTO(from, to, fromBoundary, toBoundary);
}

public DateRange {
public DateRangeDTO {
if (from.isAfter(to)) {
throw new IllegalArgumentException("Start date must be before or equal to end date.");
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,14 +9,14 @@
* @param pageSize The number of items per page (1-100)
* @param pageNumber The zero-based page number
*/
public record Page(
public record PageDTO(
@Min(1)
@Max(100)
int pageSize,
@Min(0)
int pageNumber
) {
public static Page of(int pageSize, int pageNumber) {
return new Page(pageSize, pageNumber);
public static PageDTO of(int pageSize, int pageNumber) {
return new PageDTO(pageSize, pageNumber);
}
}

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package sep490.common.api.dto;

import java.util.List;

public record SearchCriteriaDTO<T>(
PageDTO page,
List<SortDTO> sorts,
T criteria
) {
public static <T> SearchCriteriaDTO<T> of(PageDTO page, List<SortDTO> sorts, T criteria) {
return new SearchCriteriaDTO<>(page, sorts, criteria);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,11 @@

import java.util.List;

public record SearchResult<T>(
public record SearchResultDTO<T>(
@NotNull List<T> results,
@Min(0) long totalElements
) {
public static <T> SearchResult<T> of(@NotNull List<T> results, long totalElements) {
return new SearchResult<>(results, totalElements);
public static <T> SearchResultDTO<T> of(@NotNull List<T> results, long totalElements) {
return new SearchResultDTO<>(results, totalElements);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -10,13 +10,13 @@
* @param field The name of the field to sort by
* @param direction The direction (ascending or descending) to sort in
*/
public record Sort(
public record SortDTO(
@NotBlank
String field,
@NotNull
SortDirection direction
) {
public static Sort of(String field, SortDirection direction) {
return new Sort(field, direction);
public static SortDTO of(String field, SortDirection direction) {
return new SortDTO(field, direction);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package sep490.common.api.utils;

public final class CommonConstant {

// RFC5322
public static final String EMAIL_PATTERN = "^[a-zA-Z0-9_!#$%&'*+/=?`{|}~^.-]+@[a-zA-Z0-9.-]+$";

private CommonConstant() {
// Utility class. No instantiation allowed.
}
}
3 changes: 2 additions & 1 deletion sep490-idp/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ dependencies {
implementation 'org.thymeleaf.extras:thymeleaf-extras-springsecurity6'

// Database
implementation 'org.springframework.boot:spring-boot-starter-validation'
implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
implementation 'org.flywaydb:flyway-core'
implementation 'org.flywaydb:flyway-database-postgresql'
Expand All @@ -69,4 +70,4 @@ dependencies {

tasks.named('test') {
useJUnitPlatform()
}
}
2 changes: 2 additions & 0 deletions sep490-idp/src/main/java/sep490/idp/IdentityProvider.java
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,10 @@

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.data.jpa.repository.config.EnableJpaAuditing;

@SpringBootApplication
@EnableJpaAuditing
public class IdentityProvider {

public static void main(String[] args) {
Expand Down
21 changes: 21 additions & 0 deletions sep490-idp/src/main/java/sep490/idp/configs/AuditorAwareImpl.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package sep490.idp.configs;

import jakarta.annotation.Nonnull;
import lombok.RequiredArgsConstructor;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.domain.AuditorAware;
import sep490.idp.security.UserContextData;
import sep490.idp.utils.SecurityUtils;

import java.util.Optional;

@Configuration
@RequiredArgsConstructor
public class AuditorAwareImpl implements AuditorAware<String> {

@Nonnull
@Override
public Optional<String> getCurrentAuditor() {
return SecurityUtils.getUserContextData().map(UserContextData::getUsername);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,37 +3,34 @@
import jakarta.persistence.Column;
import jakarta.persistence.EntityListeners;
import jakarta.persistence.MappedSuperclass;
import lombok.EqualsAndHashCode;
import lombok.Getter;
import lombok.Setter;
import lombok.experimental.FieldNameConstants;
import org.springframework.data.annotation.CreatedBy;
import org.springframework.data.annotation.CreatedDate;
import org.springframework.data.annotation.LastModifiedBy;
import org.springframework.data.annotation.LastModifiedDate;
import org.springframework.data.jpa.domain.support.AuditingEntityListener;
import sep490.common.api.AuditableEntity;

import java.time.LocalDateTime;

@FieldNameConstants
@Getter
@Setter
@FieldNameConstants
@MappedSuperclass
@EqualsAndHashCode(callSuper = true)
@EntityListeners(AuditingEntityListener.class)
public abstract class AbstractAuditableEntity extends AbstractBaseEntity {
public abstract class AbstractAuditableEntity extends AbstractBaseEntity implements AuditableEntity {
@Column(name = "created_by", nullable = false, updatable = false)
@CreatedBy
private String createdBy;

@Column(name = "created_date", nullable = false, updatable = false)
@CreatedDate
private LocalDateTime createdDate;

@Column(name = "last_modified_date", nullable = false)
@LastModifiedDate
private LocalDateTime lastModifiedDate;

@Column(name = "last_modified_by", nullable = false)
@LastModifiedBy
private String lastModifiedBy;
Expand Down
90 changes: 58 additions & 32 deletions sep490-idp/src/main/java/sep490/idp/entity/AbstractBaseEntity.java
Original file line number Diff line number Diff line change
@@ -1,86 +1,112 @@
package sep490.idp.entity;

import jakarta.persistence.*;
import jakarta.persistence.Column;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
import jakarta.persistence.Id;
import jakarta.persistence.MappedSuperclass;
import jakarta.persistence.Transient;
import jakarta.persistence.Version;
import lombok.Getter;
import lombok.Setter;
import lombok.experimental.FieldNameConstants;
import org.hibernate.proxy.HibernateProxy;
import org.hibernate.proxy.LazyInitializer;
import sep490.common.api.BaseEntity;

import java.util.Optional;
import java.util.UUID;

@Getter
@Setter
@Getter
@FieldNameConstants
@MappedSuperclass
public abstract class AbstractBaseEntity {

public abstract class AbstractBaseEntity implements BaseEntity {
@Id
@Column(name = "id", nullable = false)
@GeneratedValue(strategy = GenerationType.UUID)
private UUID id;

@Version
@Column(name = "version", nullable = false)
private int version;

@Transient
private boolean transientHashCodeLeaked;

protected AbstractBaseEntity() {
// Abstract class should not be instantiated
}


/**
* Get the class of an instance or the underlying class
* of a proxy (without initializing the proxy!). It is
* almost always better to use the entity name!
*/
private static Class<?> getPersistenceClassWithoutInitializingProxy(Object entity) {
if (entity instanceof HibernateProxy proxy) {
LazyInitializer li = proxy.getHibernateLazyInitializer();
return li.getPersistentClass();
} else {
return entity.getClass();
}
}

public boolean isPersisted() {
return Optional.ofNullable(getId()).isPresent();
}

@Override
public int hashCode() {
if (!isPersisted()) { // is new or is in transient state.
transientHashCodeLeaked = true;
return -super.hashCode();
}

// Because hashcode has just been asked for when the object is in transient state at that time super.hashCode() is returned.
// Now for consistency, we return the same value.
if (transientHashCodeLeaked) {
return -super.hashCode();
}

// The above mechanism obey the rule: if 2 objects are equal, their hashcode must be same.
return getId().hashCode();
}


@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}

if (obj == null) {
return false;
}

/*
* The following is a solution that works for hibernate lazy loading proxies.
*/
if (JpaUtils.getPersistenceClassWithoutInitializingProxy(this) != JpaUtils.getPersistenceClassWithoutInitializingProxy(obj)) {
if (getPersistenceClassWithoutInitializingProxy(this) != getPersistenceClassWithoutInitializingProxy(obj)) {
return false;
}

/*
* To check whether the class of the argument is equal (or compatible) to the implementing class before
* casting it
* */
if (getClass() != obj.getClass()) {
return false;
}

AbstractBaseEntity other = (AbstractBaseEntity) obj;
if (isPersisted() && other.isPersisted()) { // both entities are not new
return getId().equals(other.getId());
}
return false;
}


@Override
@SuppressWarnings("java:S2676") // In case current entity state is transient => should return a negative number
public int hashCode() {
if (!isPersisted()) { // is new or is in transient state.
transientHashCodeLeaked = true;
return -super.hashCode();
}

// Because hashcode has just been asked for when the object is in transient state at that time super.hashCode() is returned.
// Now for consistency, we return the same value.
if (transientHashCodeLeaked) {
return -super.hashCode();
}

// The above mechanism obey the rule: if 2 objects are equal, their hashcode must be same.
return getId().hashCode();
}

@Override
public String toString() {
return getClass().getSimpleName() + "(" + getId() + ")";
Expand Down
Loading

0 comments on commit 829e596

Please sign in to comment.