Skip to content

Commit

Permalink
Add JPA inc auditing
Browse files Browse the repository at this point in the history
  • Loading branch information
flawmop committed Aug 31, 2024
1 parent e7b6e25 commit 7721522
Show file tree
Hide file tree
Showing 14 changed files with 314 additions and 19 deletions.
27 changes: 23 additions & 4 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,8 @@ gitProperties {

ext {
set('springCloudVersion', "2023.0.0")
set('testKeycloakVersion', "2.3.0")
set('tcKeycloakVersion', "2.3.0")
set('tcPostgresVersion', "1.17.3")
}

configurations {
Expand All @@ -44,21 +45,24 @@ configurations {

dependencies {
implementation 'io.micrometer:micrometer-registry-prometheus:1.13.3'
implementation 'org.springframework.boot:spring-boot-starter-aop'
implementation 'org.springframework.boot:spring-boot-starter-actuator'
implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
implementation 'org.springframework.boot:spring-boot-starter-security'
implementation 'org.springframework.boot:spring-boot-starter-oauth2-resource-server'
implementation 'org.springframework.boot:spring-boot-starter-web'
// Note: Introduces commons-logging conflict with spring-boot-starter-web!
implementation 'org.springframework.cloud:spring-cloud-stream-binder-rabbit'

runtimeOnly 'org.postgresql:postgresql'

testImplementation 'org.springframework.boot:spring-boot-starter-test'
testImplementation 'io.projectreactor:reactor-test'
}

dependencyManagement {
imports {
mavenBom "org.springframework.cloud:spring-cloud-dependencies:${springCloudVersion}"
mavenBom "org.testcontainers:testcontainers-bom:${tcPostgresVersion}"
}
}

Expand All @@ -83,10 +87,17 @@ testing {
java {
srcDirs = ['src/test-i/java']
}
resources {
srcDirs = ['src/test-i/resources']
}
}
dependencies {
implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
implementation 'org.springframework.boot:spring-boot-starter-oauth2-resource-server'
implementation 'org.springframework.boot:spring-boot-testcontainers'
implementation 'org.springframework.security:spring-security-test'
implementation 'org.testcontainers:junit-jupiter'
implementation "org.testcontainers:postgresql:${tcPostgresVersion}"
}
}

Expand All @@ -100,10 +111,18 @@ testing {
}
}
dependencies {
implementation "com.github.dasniko:testcontainers-keycloak:${tcKeycloakVersion}"
implementation 'org.springframework.boot:spring-boot-starter-oauth2-resource-server'
implementation 'org.springframework.security:spring-security-test'
implementation 'org.testcontainers:junit-jupiter'
implementation "com.github.dasniko:testcontainers-keycloak:${testKeycloakVersion}"
implementation "org.testcontainers:postgresql:${tcPostgresVersion}"
}
targets.configureEach {
testTask.configure {
environment("SPRING_PROFILES_ACTIVE", "e2e")
// For extra debugging, use below with logging.level.root=TRACE in application-e2e.yml
// testLogging.showStandardStreams = true
}
}
}
}
Expand Down Expand Up @@ -150,4 +169,4 @@ tasks.named('test') {

tasks.withType(JavaCompile) {
options.encoding = 'UTF-8'
}
}
Original file line number Diff line number Diff line change
@@ -1,14 +1,13 @@
package com.insilicosoft.portal.svc.rip.config;

import java.util.concurrent.Executor;

import org.slf4j.Logger ;
import org.slf4j.LoggerFactory ;
import org.springframework.beans.factory.annotation.Value ;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.annotation.EnableAsync;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import org.springframework.security.task.DelegatingSecurityContextAsyncTaskExecutor;

/**
* Asynchronous configuration.
Expand Down Expand Up @@ -46,12 +45,13 @@ public class AsyncConfig {
}

/**
* Create the {@link ThreadPoolTaskExecutor}.
* Create a {@link DelegatingSecurityContextAsyncTaskExecutor} as we want to be passing a
* security {@code Authentication} object to asynchronous methods (e.g. for JPA auditing purposes).
*
* @return Created executor.
*/
@Bean
Executor taskExecutor() {
DelegatingSecurityContextAsyncTaskExecutor taskExecutor() {
final ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
executor.setCorePoolSize(corePoolSize);
executor.setMaxPoolSize(maxPoolSize);
Expand All @@ -62,7 +62,7 @@ Executor taskExecutor() {
log.debug("~taskExecutor() : Executor core pool size '{}', max pool size '{}', queue capacity '{}', thread name prefix '{}'",
corePoolSize, maxPoolSize, queueCapacity, threadNamePrefix);

return executor;
return new DelegatingSecurityContextAsyncTaskExecutor(executor);
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
package com.insilicosoft.portal.svc.rip.config;

import java.util.Optional;

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;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.oauth2.server.resource.authentication.JwtAuthenticationToken;

/**
* JPA Auditing configuration.
* <p>
* Contains mechanism to map the JWT token claims to an identity for auditing purposes.
*
* @author geoff
*/
@Configuration
@EnableJpaAuditing(auditorAwareRef = "auditorAware")
public class JpaAuditingConfig {

@Bean
AuditorAware<String> auditorAware() {
return new AuditorAware<String>() {
@Override
public Optional<String> getCurrentAuditor() {
String auditor = null;
if (SecurityContextHolder.getContext().getAuthentication() != null) {
JwtAuthenticationToken token = (JwtAuthenticationToken) SecurityContextHolder.getContext().getAuthentication();
// Use the JWT 'subject' claim as identifier for audit purposes!
auditor = token.getName();
}
return Optional.ofNullable(auditor);
}
};
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ public class FileAsyncUploadController {
*
* @param inputProcessorService Input processing implementation.
*/
public FileAsyncUploadController(InputProcessorService inputProcessorService) {
public FileAsyncUploadController(final InputProcessorService inputProcessorService) {
this.inputProcessorService = inputProcessorService;
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
package com.insilicosoft.portal.svc.rip.persistence.entity;

import static jakarta.persistence.GenerationType.IDENTITY;

import java.time.Instant;

import org.springframework.data.jpa.domain.support.AuditingEntityListener;

import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.EntityListeners;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.Id;
import jakarta.persistence.Table;
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.annotation.Version;

/**
* Simulation entity.
*
* @author geoff
*/
@Entity
@EntityListeners(AuditingEntityListener.class)
@Table(name = "simulation")
public class Simulation {

@Id
@Column(name = "id")
@GeneratedValue(strategy = IDENTITY)
private Long entityId;

// See JPA auditing
@CreatedDate
Instant createdDate;

// See JPA auditing
@LastModifiedDate
Instant lastModifiedDate;

// See JPA auditing
@CreatedBy
String createdBy;

// See JPA auditing
@LastModifiedBy
String lastModifiedBy;

@Version
int version;

public Simulation() {}

// Getters/Setters

/**
* Retrieve the entity id.
*
* @return Id of entity.
*/
public Long getEntityId() {
return entityId;
}

// Boilerplate implementations

@Override
public String toString() {
return "Simulation [entityId=" + entityId + ", createdDate=" + createdDate + ", lastModifiedDate="
+ lastModifiedDate + ", createdBy=" + createdBy + ", lastModifiedBy=" + lastModifiedBy + ", version=" + version
+ "]";
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package com.insilicosoft.portal.svc.rip.persistence.repository;

import org.springframework.data.jpa.repository.JpaRepository;

import com.insilicosoft.portal.svc.rip.persistence.entity.Simulation;

/**
* Repository for {@link Simulation} objects.
*
* @author geoff
*/
public interface SimulationRepository extends JpaRepository<Simulation, Long> {

}
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
package com.insilicosoft.portal.svc.rip.service;

import org.springframework.security.core.Authentication;

import com.insilicosoft.portal.svc.rip.exception.FileProcessingException;

/**
Expand All @@ -13,9 +15,12 @@ public interface InputProcessorService {
public String get();

/**
* Process a file asyncronously (e.g. using the method {@code @Async} notation) a byte array).
* Process a file asynchronously (e.g. using the method {@code @Async} notation) a byte array).
* <p>
* For auditing purposes, there <b>MUST</b> be an {@link Authentication} object returned by
* {@code SecurityContextHolder.getContext().getAuthentication()} when this method runs.
*
* @param file Byte array file.
* @param file Non-{@code null} byte array.
* @throws FileProcessingException If file processing problems.
*/
public void processAsync(byte[] file) throws FileProcessingException;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@
import com.fasterxml.jackson.core.JsonToken;
import com.insilicosoft.portal.svc.rip.event.SimulationMessage;
import com.insilicosoft.portal.svc.rip.exception.FileProcessingException;
import com.insilicosoft.portal.svc.rip.persistence.entity.Simulation;
import com.insilicosoft.portal.svc.rip.persistence.repository.SimulationRepository;

enum FieldsSections {
simulations
Expand All @@ -40,14 +42,18 @@ public class InputProcessorServiceImpl implements InputProcessorService {

private static final Logger log = LoggerFactory.getLogger(InputProcessorServiceImpl.class);

private final SimulationRepository simulationRepository;
private final StreamBridge streamBridge;

/**
* Initialising constructor.
*
* @param simulationRepository Simulation repository.
* @param streamBridge Event sending mechanism.
*/
public InputProcessorServiceImpl(StreamBridge streamBridge) {
public InputProcessorServiceImpl(final SimulationRepository simulationRepository,
final StreamBridge streamBridge) {
this.simulationRepository = simulationRepository;
this.streamBridge = streamBridge;
}

Expand Down Expand Up @@ -94,11 +100,13 @@ public void processAsync(final byte[] file) throws FileProcessingException {

// Verify the input was good

// Record the simulation
log.debug("~processAsync() : Saved : " + simulationRepository.save(new Simulation()).toString());

// Fire off events for, e.g. simulation runners and databases
for (SimulationMessage simulationMessage: simulations) {
streamBridge.send(BINDING_NAME_SIMULATION_INPUT, simulationMessage);
}

}

void parseSimulations(JsonParser jsonParser, List<SimulationMessage> simulations) throws IOException {
Expand Down
10 changes: 10 additions & 0 deletions src/main/resources/application.yml
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,16 @@ spring:
# Name below referenced internally and by StreamBridge
simulation-input:
destination: simulation-invoke
datasource:
hikari:
connection-timeout: 2000 #ms
maximum-pool-size: 5
password: password
url: jdbc:postgresql://localhost:5432/portaldb
username: user
jpa:
hibernate:
ddl-auto: create-drop
rabbitmq:
host: localhost
port: 5672
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
import org.springframework.security.oauth2.jwt.JwtDecoder;

@SpringBootTest
class RipSvcApplicationTests {
class RipSvcApplicationE2E {

@MockBean
private JwtDecoder jwtDecoder;
Expand Down
3 changes: 3 additions & 0 deletions src/test-e2e/resources/application-e2e.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
spring:
datasource:
url: jdbc:tc:postgresql:14.4:///
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
package com.insilicosoft.portal.svc.rip.controller;

import static org.mockito.BDDMockito.given;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.jwt;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
//import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print;
Expand Down Expand Up @@ -39,9 +39,8 @@ public class FileAsyncUploadControllerIT {

@MockBean
private InputProcessorService mockInputProcessorService;

@MockBean
private JwtDecoder jwtDecoder;
private JwtDecoder mockJwtDecoder;

@DisplayName("Test GET method(s)")
@Nested
Expand All @@ -51,7 +50,7 @@ class GetMethods {
void testGet() throws Exception {
final String getMessage = "Message from get!";

given(mockInputProcessorService.get()).willReturn(getMessage);
when(mockInputProcessorService.get()).thenReturn(getMessage);

mockMvc.perform(get(RipIdentifiers.REQUEST_MAPPING_RUN).with(jwt().authorities(userRole)))
//.andDo(print())
Expand Down
Loading

0 comments on commit 7721522

Please sign in to comment.