Skip to content

Commit

Permalink
Audit Initial Setup
Browse files Browse the repository at this point in the history
  • Loading branch information
garvit-joshi committed Nov 13, 2023
1 parent 082cde5 commit 109779d
Show file tree
Hide file tree
Showing 18 changed files with 498 additions and 10 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ build/
# Log file
*.log
/log.file_IS_UNDEFINED
*.file_IS_UNDEFINED


# BlueJ files
Expand Down
6 changes: 0 additions & 6 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -98,12 +98,6 @@
<groupId>org.springdoc</groupId>
<artifactId>springdoc-openapi-starter-webmvc-ui</artifactId>
<version>2.2.0</version>
<exclusions>
<exclusion>
<artifactId>commons-lang3</artifactId>
<groupId>org.apache.commons</groupId>
</exclusion>
</exclusions>
</dependency>
</dependencies>

Expand Down
4 changes: 4 additions & 0 deletions src/main/java/org/garvit/IronHide/IronHideApplication.java
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,11 @@

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.data.jpa.repository.config.EnableJpaRepositories;
import org.springframework.kafka.annotation.EnableKafka;

@EnableKafka
@EnableJpaRepositories
@SpringBootApplication
public class IronHideApplication {

Expand Down
14 changes: 14 additions & 0 deletions src/main/java/org/garvit/IronHide/constants/SecurityConstants.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package org.garvit.IronHide.constants;

public class SecurityConstants {

// String Constants
public static final String APPLICATION_NAME_BLANK_MESSAGE =
"Application Name should not be blank";
public static final String UUID_BLANK_MESSAGE = "UUID can not be blank";
public static final String UUID_NOT_VALID_MESSAGE = "UUID in not valid";
public static final String CALLER_BLANK_MESSAGE = "Caller should not be blank";
public static final String CONTEXT_BLANK_MESSAGE = "Context should not be blank";
public static final String AUDITOR_BLANK_MESSAGE = "Auditor should not be blank";
public static final String BLOB_BLANK_MESSAGE = "Blob should not be blank";
}
34 changes: 34 additions & 0 deletions src/main/java/org/garvit/IronHide/controllers/AuditController.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
package org.garvit.IronHide.controllers;

import lombok.extern.slf4j.Slf4j;
import org.garvit.IronHide.models.AuditDTO;
import org.garvit.IronHide.models.AuditPublishDTO;
import org.garvit.IronHide.utilities.ValidationUtils;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;

import java.util.UUID;


/**
* @author garvit-joshi on 13/11/23
*/
@Slf4j
@RestController
@RequestMapping("/v1/api/audits")
public class AuditController {

@PostMapping("/save")
public ResponseEntity<UUID> saveAudit(@RequestBody AuditPublishDTO auditPublishDTO) {
log.info("Request for saving audit for uuid: {}", auditPublishDTO.getUuid());
ValidationUtils.validateObject(auditPublishDTO, true);
// Add Service layer here
return ResponseEntity.ok(UUID.randomUUID());
}

@GetMapping("/{uuid}")
public ResponseEntity<AuditDTO> getAuditByUUID(@PathVariable String uuid) {
// Add Service layer here
return ResponseEntity.ok(null);
}
}
31 changes: 31 additions & 0 deletions src/main/java/org/garvit/IronHide/controllers/BaseController.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
package org.garvit.IronHide.controllers;

import lombok.val;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequestMapping("/")
public class BaseController {

@Value("${spring.application.name}")
private String appName;

@Value("${git.commit.id.full}")
private String commitId;

@Value("${git.branch}")
private String branch;

@GetMapping
public ResponseEntity<String> homePage() {
val message =
String.format(
"<h4>Hello World!</h4> <pre word-wrap: break-word; white-space: pre-wrap;>This is %s. <br>Branch: %s <br>Commit: %s<pre>",
appName, branch, commitId);
return ResponseEntity.ok(message);
}
}
13 changes: 13 additions & 0 deletions src/main/java/org/garvit/IronHide/entity/AuditEntity.java
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,19 @@
import io.hypersistence.utils.hibernate.type.json.JsonBinaryType;
import jakarta.persistence.*;
import java.util.Date;

import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
import org.garvit.IronHide.models.AuditContext;
import org.hibernate.annotations.Type;

@Entity
@Getter
@Setter
@AllArgsConstructor
@NoArgsConstructor
@Table(name = "audit")
public class AuditEntity {

Expand All @@ -16,13 +25,17 @@ public class AuditEntity {
@Column(name = "id")
private Long id;

@Column(name = "uuid", nullable = false, length = 128)
private String uuid;

@Column(name = "application", nullable = false, length = 32)
private String application;

@Column(name = "caller", nullable = false, length = 32)
private String caller;

@Column(name = "audit_context", nullable = false, length = 8)
@Enumerated(EnumType.STRING)
private AuditContext context;

@Column(name = "time", nullable = false)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
package org.garvit.IronHide.exceptions;


import org.garvit.IronHide.models.APIInfo;
import jakarta.validation.ValidationException;
import jakarta.validation.constraints.NotNull;
import java.util.Objects;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.HttpStatusCode;
import org.springframework.http.ResponseEntity;
import org.springframework.web.HttpRequestMethodNotSupportedException;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.context.request.WebRequest;
import org.springframework.web.servlet.mvc.method.annotation.ResponseEntityExceptionHandler;

/**
* @author garvit-joshi on 12/11/23
*/
@Slf4j
@ControllerAdvice
public class ControllerAdvisor extends ResponseEntityExceptionHandler {

private static ResponseEntity<APIInfo<String>> throwBadRequest(Exception ex) {
log.error(ex.getMessage(), ex);
APIInfo<String> apiInfo = new APIInfo<>(HttpStatus.BAD_REQUEST, ex.getLocalizedMessage());
return ResponseEntity.status(apiInfo.status()).body(apiInfo);
}

@Override
protected ResponseEntity<Object> handleHttpRequestMethodNotSupported(
@NotNull HttpRequestMethodNotSupportedException ex,
@NotNull HttpHeaders headers,
@NotNull HttpStatusCode status,
@NotNull WebRequest request) {
var responseMessage = new StringBuilder();
responseMessage.append(ex.getMethod());
responseMessage.append(" method is not supported for this request. Supported methods are ");
Objects.requireNonNull(ex.getSupportedHttpMethods())
.forEach(t -> responseMessage.append(t).append(" "));

APIInfo<String> apiInfo =
new APIInfo<>(HttpStatus.METHOD_NOT_ALLOWED, responseMessage.toString());
return ResponseEntity.status(apiInfo.status()).body(apiInfo);
}

@ExceptionHandler(ValidationException.class)
public ResponseEntity<APIInfo<String>> handleValidationException(
ValidationException ex, WebRequest request) {
return throwBadRequest(ex);
}

@ExceptionHandler(IllegalArgumentException.class)
public ResponseEntity<APIInfo<String>> handleIllegalArgumentException(
IllegalArgumentException ex, WebRequest request) {
return throwBadRequest(ex);
}

@ExceptionHandler(IllegalStateException.class)
public ResponseEntity<APIInfo<String>> handleIllegalStateException(
IllegalStateException ex, WebRequest request) {
return throwBadRequest(ex);
}

@ExceptionHandler(Exception.class)
public ResponseEntity<APIInfo<String>> handleAll(Exception ex, WebRequest request) {
log.error(ex.getMessage(), ex);
APIInfo<String> apiInfo =
new APIInfo<>(HttpStatus.INTERNAL_SERVER_ERROR, ex.getLocalizedMessage());
return ResponseEntity.status(apiInfo.status()).body(apiInfo);
}
}

Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
package org.garvit.IronHide.kafka.consumers;

import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.kafka.clients.consumer.ConsumerRecord;
import org.garvit.IronHide.models.AuditPublishDTO;
import org.garvit.IronHide.utilities.ValidationUtils;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.kafka.annotation.KafkaListener;
import org.springframework.stereotype.Component;

@Slf4j
@Component
@RequiredArgsConstructor
@ConditionalOnProperty(value = "spring.kafka.enabled", havingValue = "true", matchIfMissing = true)
public class AuditConsumer {

/**
* Kafka listener for the "audit" topic. This method processes messages received on this topic.
* The key of each message is expected to be a UUID, which uniquely identifies each message. Using
* UUIDs as keys facilitates easy tracking and logging of message consumption, enhancing
* traceability and debuggability.
*
* @see <a
* href="https://forum.confluent.io/t/what-should-i-use-as-the-key-for-my-kafka-message/312/1">Key
* for kafka Messages</a>
* @param auditPublishConsumer The ConsumerRecord object, containing the UUID key and the
* AuditPublishDTO message.
*/
@KafkaListener(topics = "audit", containerFactory = "auditKafkaListenerContainerFactory")
public void auditListener(ConsumerRecord<String, AuditPublishDTO> auditPublishConsumer) {
log.info("Consuming listener for uuid: {}", auditPublishConsumer.key());
ValidationUtils.validateObject(auditPublishConsumer.value(), true);

// Add Service layer here
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
package org.garvit.IronHide.kafka.consumers;

import com.fasterxml.jackson.databind.ObjectMapper;
import java.util.HashMap;
import java.util.Map;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.kafka.clients.consumer.ConsumerConfig;
import org.apache.kafka.common.serialization.StringDeserializer;
import org.garvit.IronHide.models.AuditPublishDTO;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.kafka.annotation.EnableKafka;
import org.springframework.kafka.config.ConcurrentKafkaListenerContainerFactory;
import org.springframework.kafka.core.ConsumerFactory;
import org.springframework.kafka.core.DefaultKafkaConsumerFactory;
import org.springframework.kafka.listener.DefaultErrorHandler;
import org.springframework.kafka.support.ExponentialBackOffWithMaxRetries;
import org.springframework.kafka.support.serializer.JsonDeserializer;

@Slf4j
@Configuration
@RequiredArgsConstructor
public class ConsumerConfigurations {

private final ObjectMapper objectMapper;

@Value("${spring.application.name}")
private String clientId;

@Value(value = "${spring.kafka.bootstrap-servers}")
private String bootstrapAddress;

/** Consumer Factory for consuming Audit */
@Bean
public ConcurrentKafkaListenerContainerFactory<String, AuditPublishDTO>
auditKafkaListenerContainerFactory() {
ConcurrentKafkaListenerContainerFactory<String, AuditPublishDTO> factory =
new ConcurrentKafkaListenerContainerFactory<>();
factory.setConsumerFactory(auditConsumerFactory());
factory.setConcurrency(2);
factory.setCommonErrorHandler(
new DefaultErrorHandler(
(record, e) -> {
log.error(
"Exception while processing Audit Update Event with key: {} and value {}, Retrying",
record.key(),
record.value());
log.error(e.getMessage(), e);
},
new ExponentialBackOffWithMaxRetries(3)));
return factory;
}

private ConsumerFactory<String, AuditPublishDTO> auditConsumerFactory() {
Map<String, Object> props = new HashMap<>();
props.put(ConsumerConfig.BOOTSTRAP_SERVERS_CONFIG, bootstrapAddress);
props.put(ConsumerConfig.GROUP_ID_CONFIG, clientId);
props.put(ConsumerConfig.MAX_POLL_RECORDS_CONFIG, 500);
return new DefaultKafkaConsumerFactory<>(
props,
new StringDeserializer(),
new JsonDeserializer<>(AuditPublishDTO.class, objectMapper));
}
}
9 changes: 9 additions & 0 deletions src/main/java/org/garvit/IronHide/models/APIInfo.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package org.garvit.IronHide.models;

import org.springframework.http.HttpStatus;

/**
* @author garvit-joshi on 16/07/23
*/
public record APIInfo<T>(HttpStatus status, T message) {}

27 changes: 23 additions & 4 deletions src/main/java/org/garvit/IronHide/models/AuditPublishDTO.java
Original file line number Diff line number Diff line change
@@ -1,12 +1,16 @@
package org.garvit.IronHide.models;

import jakarta.validation.constraints.NotBlank;
import java.io.Serial;
import java.io.Serializable;

import jakarta.validation.constraints.NotNull;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;

import java.io.Serial;
import java.io.Serializable;
import org.garvit.IronHide.constants.SecurityConstants;
import org.hibernate.validator.constraints.UUID;

@Getter
@Setter
Expand All @@ -16,11 +20,26 @@ public class AuditPublishDTO implements Serializable {

@Serial private static final long serialVersionUID = -417463532907899400L;

@NotBlank(message = SecurityConstants.UUID_BLANK_MESSAGE)
@UUID(message = SecurityConstants.UUID_NOT_VALID_MESSAGE)
protected String uuid;

@NotBlank(message = SecurityConstants.APPLICATION_NAME_BLANK_MESSAGE)
protected String application;

@NotBlank(message = SecurityConstants.CALLER_BLANK_MESSAGE)
protected String caller;

@NotNull(message = SecurityConstants.CONTEXT_BLANK_MESSAGE)
protected AuditContext context;
protected long time;

protected long timeMillis;

@NotBlank(message = SecurityConstants.AUDITOR_BLANK_MESSAGE)
protected String auditor;

@NotBlank(message = SecurityConstants.BLOB_BLANK_MESSAGE)
protected String blob;

protected String additionalMetadata;
}
Loading

0 comments on commit 109779d

Please sign in to comment.