Skip to content

Commit

Permalink
add implementation of custom spring events based on some internal logic
Browse files Browse the repository at this point in the history
  • Loading branch information
EMEA\VEA3SF committed Jan 21, 2024
1 parent ce9bf21 commit 1754eb7
Show file tree
Hide file tree
Showing 12 changed files with 434 additions and 236 deletions.
21 changes: 11 additions & 10 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,10 @@ CRUD operations</b>.Also is demonstrated how to return a <b>Pageable response</b
etc.Created <b>Global exception handler</b> for handle exception from the codebase and custom spring validator(example
with annotation for field and class level).Demo of how to use CriteriaBuilder for data retrieving.Added configuration for
collecting application metrics via Spring Boot Actuator and configuration for Injecting Git Information Into Spring.
In the project you can find implementation of Quartz scheduler using Cron Triggers and Expressions and Spring Custom Events.

# GitHub actions pipelines integration
Added integration with GitHub actions for code analyzis with sonarcloud and codecov for code coverage report
Added integration with GitHub actions for code analyses with sonarcloud and codecov for code coverage report
Added integration with Docker.On every push event to main branch this action generates a new docker image with the relevant image tag.

- https://www.sonarsource.com/products/sonarcloud/
Expand All @@ -38,16 +39,16 @@ Added integration with Docker.On every push event to main branch this action gen

# Provided examples with implementation on SpringBoot.Links for more info:

- PageAble and sorting response: https://www.baeldung.com/spring-data-jpa-pagination-sorting
- Flyway: https://www.baeldung.com/database-migrations-with-flyway
- JUNIT5: https://www.baeldung.com/junit-5
- Global exception handler in JAVA: https://www.baeldung.com/java-global-exception-handler
- Actuator: https://www.baeldung.com/spring-boot-actuators
- CriteriaBuilder: https://www.baeldung.com/spring-data-criteria-queries
- Custom validator: https://www.baeldung.com/spring-mvc-custom-validator
- PageAble and sorting response: https://www.baeldung.com/spring-data-jpa-pagination-sorting → (controller package)
- Flyway: https://www.baeldung.com/database-migrations-with-flyway → (resources.db.migration package)
- JUNIT5: https://www.baeldung.com/junit-5 → (tests package)
- Global exception handler in JAVA: https://www.baeldung.com/java-global-exception-handler → (advice package)
- CriteriaBuilder: https://www.baeldung.com/spring-data-criteria-queries → (repository.custom package and CriteriaBuilderExampleController)
- Custom validator: https://www.baeldung.com/spring-mvc-custom-validator → (validator package)
- Spring Boot Actuator: https://www.baeldung.com/spring-boot-actuators
- Injecting Git Information Into Spring: https://www.baeldung.com/spring-git-information
- Quartz Job Scheduler with Cron Triggers and Expressions: https://www.quartz-scheduler.org/
- Injecting Git Information Into Spring: https://www.baeldung.com/spring-git-information → (HistoryLinkProvidingGitInfoContributor.class)
- Quartz Job Scheduler with Cron Triggers and Expressions: https://www.quartz-scheduler.org/ → (configuration.quartz package)
- Spring Custom Events: https://www.baeldung.com/spring-events → (event package)

# Branches:
- Branch with name: "java11Version" is set up for JDK11
Expand Down
20 changes: 20 additions & 0 deletions src/main/java/com/springpageable/dto/ProcessEventDto.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package com.springpageable.dto;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

import java.io.Serial;
import java.io.Serializable;
import java.util.UUID;

@Data
@NoArgsConstructor
@AllArgsConstructor
public class ProcessEventDto implements Serializable {

@Serial
private static final long serialVersionUID = 1L;

private UUID nextAutoPlanSubJobUuid;
}
20 changes: 20 additions & 0 deletions src/main/java/com/springpageable/event/ProcessEvent.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package com.springpageable.event;

import com.springpageable.dto.ProcessEventDto;
import com.springpageable.model.FutureDevice;
import lombok.EqualsAndHashCode;
import lombok.Getter;
import org.springframework.context.ApplicationEvent;

@Getter
@EqualsAndHashCode(callSuper = true)
public class ProcessEvent extends ApplicationEvent {

private final ProcessEventDto processEventDto;

public ProcessEvent(FutureDevice futureDevice, ProcessEventDto processEventDto) {
super(futureDevice);
this.processEventDto = processEventDto;
}

}
17 changes: 17 additions & 0 deletions src/main/java/com/springpageable/event/ProcessEventListener.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package com.springpageable.event;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.context.ApplicationListener;
import org.springframework.stereotype.Component;

@Component
public class ProcessEventListener implements ApplicationListener<ProcessEvent> {

private static final Logger LOGGER = LoggerFactory.getLogger(ProcessEventListener.class);

@Override
public void onApplicationEvent(ProcessEvent event) {
LOGGER.info("Received spring custom event - {}", event.getProcessEventDto());
}
}
24 changes: 24 additions & 0 deletions src/main/java/com/springpageable/event/ProcessEventPublisher.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package com.springpageable.event;

import com.springpageable.dto.ProcessEventDto;
import com.springpageable.model.FutureDevice;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.stereotype.Component;

import java.util.UUID;

@Component
public class ProcessEventPublisher {

private ApplicationEventPublisher applicationEventPublisher;

public ProcessEventPublisher(ApplicationEventPublisher applicationEventPublisher) {
this.applicationEventPublisher = applicationEventPublisher;
}

public void publishCustomEvent(FutureDevice futureDevice) {
ProcessEventDto processEventDto = new ProcessEventDto(UUID.randomUUID());
var event = new ProcessEvent(futureDevice, processEventDto);
applicationEventPublisher.publishEvent(event);
}
}
153 changes: 81 additions & 72 deletions src/main/java/com/springpageable/service/FutureDeviceService.java
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import com.springpageable.dto.FutureDeviceDTO;
import com.springpageable.dto.GetFutureDeviceResponseDTO;
import com.springpageable.event.ProcessEventPublisher;
import com.springpageable.exception.ConflictException;
import com.springpageable.exception.ResourceNotFoundException;
import com.springpageable.model.mapper.FutureDeviceMapper;
Expand All @@ -15,87 +16,95 @@
import org.springframework.data.domain.Pageable;
import org.springframework.stereotype.Service;

import java.util.stream.Collectors;

@Service
@Slf4j
public class FutureDeviceService {

private final FutureDeviceRepository futureDeviceRepository;
private final UserRepository userRepository;
private final FutureDeviceRepository futureDeviceRepository;
private final UserRepository userRepository;

private final FutureDeviceMapper futureDeviceMapper;

private final FutureDeviceMapper futureDeviceMapper;
private final ProcessEventPublisher applicationEventPublisher;

@Autowired
public FutureDeviceService(
FutureDeviceRepository futureDeviceRepository, FutureDeviceMapper futureDeviceMapper,UserRepository userRepository) {
this.futureDeviceRepository = futureDeviceRepository;
this.futureDeviceMapper = futureDeviceMapper;
this.userRepository=userRepository;
}
@Autowired
public FutureDeviceService(
FutureDeviceRepository futureDeviceRepository, FutureDeviceMapper futureDeviceMapper,
UserRepository userRepository,
ProcessEventPublisher applicationEventPublisher) {
this.futureDeviceRepository = futureDeviceRepository;
this.futureDeviceMapper = futureDeviceMapper;
this.userRepository = userRepository;
this.applicationEventPublisher = applicationEventPublisher;
}

/**
* Find all future devices
*
* @param p - pagination object containing page size, page number and sort parameters
* @param searchParameter - term to search by serialNumber, productId or customerName
* @return Page of {@link GetFutureDeviceResponseDTO}
*/
public Page<GetFutureDeviceResponseDTO> retrieveFutureDevices(
Pageable p, String searchParameter) {
var futureDevices = futureDeviceRepository.findFutureDevices(p,searchParameter);
if (futureDevices.isEmpty()) {
return Page.empty();
/**
* Find all future devices
*
* @param p
* - pagination object containing page size, page number and sort parameters
* @param searchParameter
* - term to search by serialNumber, productId or customerName
* @return Page of {@link GetFutureDeviceResponseDTO}
*/
public Page<GetFutureDeviceResponseDTO> retrieveFutureDevices(
Pageable p, String searchParameter) {
var futureDevices = futureDeviceRepository.findFutureDevices(p, searchParameter);
if (futureDevices.isEmpty()) {
return Page.empty();
}
var devices =
futureDevices.stream()
.map(futureDeviceMapper::futureDeviceToFutureDeviceResponseDTO)
.toList();
// trigger custom spring event
applicationEventPublisher.publishCustomEvent(futureDevices.stream().findFirst().orElse(null));
return new PageImpl<>(devices, p, devices.size());
}
var devices =
futureDevices.stream()
.map(futureDeviceMapper::futureDeviceToFutureDeviceResponseDTO)
.collect(Collectors.toList());
return new PageImpl<>(devices, p, devices.size());
}

/**
* Creates new record in device future table, containing combination between
* serialNumber,productId and customerId
*
* @param futureDeviceDTO - {@link FutureDeviceDTO} object containing the new data
*/
public void createFutureDevice(FutureDeviceDTO futureDeviceDTO) {
userRepository.findById(futureDeviceDTO.getCustomerId()).orElseThrow(() -> {
String errMsg = String.format("There is no customer with id %s", futureDeviceDTO.getCustomerId());
log.error(errMsg);
throw new ResourceNotFoundException(errMsg);
});
var futureDevice = futureDeviceMapper.futureDeviceDTOToFutureDevice(futureDeviceDTO);
try {
futureDeviceRepository.save(futureDevice);
} catch (DataIntegrityViolationException e) {
String errMsg =
String.format(
"Combination with serial number %s,productId %s and customerId %d already exists",
futureDeviceDTO.getSerialNumber(),
futureDeviceDTO.getProductId(),
futureDeviceDTO.getCustomerId());
log.error(errMsg);
throw new ConflictException(errMsg);
/**
* Creates new record in device future table, containing combination between serialNumber,productId and customerId
*
* @param futureDeviceDTO
* - {@link FutureDeviceDTO} object containing the new data
*/
public void createFutureDevice(FutureDeviceDTO futureDeviceDTO) {
userRepository.findById(futureDeviceDTO.getCustomerId()).orElseThrow(() -> {
String errMsg = String.format("There is no customer with id %s", futureDeviceDTO.getCustomerId());
log.error(errMsg);
throw new ResourceNotFoundException(errMsg);
});
var futureDevice = futureDeviceMapper.futureDeviceDTOToFutureDevice(futureDeviceDTO);
try {
futureDeviceRepository.save(futureDevice);
} catch (DataIntegrityViolationException e) {
String errMsg =
String.format(
"Combination with serial number %s,productId %s and customerId %d already exists",
futureDeviceDTO.getSerialNumber(),
futureDeviceDTO.getProductId(),
futureDeviceDTO.getCustomerId());
log.error(errMsg);
throw new ConflictException(errMsg);
}
}
}

/**
* Deletes a future device
*
* @param id - id of the device to be deleted
*/
public void deleteFutureDevice(long id) {
var futureDevice =
futureDeviceRepository
.findById(id)
.orElseThrow(
() -> {
String errMsg = String.format("No future device found for id: %d", id);
log.error(errMsg);
throw new ResourceNotFoundException(errMsg);
});
futureDeviceRepository.deleteById(futureDevice.getId());
}
/**
* Deletes a future device
*
* @param id
* - id of the device to be deleted
*/
public void deleteFutureDevice(long id) {
var futureDevice =
futureDeviceRepository
.findById(id)
.orElseThrow(
() -> {
String errMsg = String.format("No future device found for id: %d", id);
log.error(errMsg);
throw new ResourceNotFoundException(errMsg);
});
futureDeviceRepository.deleteById(futureDevice.getId());
}
}
1 change: 0 additions & 1 deletion src/main/resources/application.yml
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,6 @@ spring:
sharedCache:
mode: ENABLE_SELECTIVE
hibernate:
dialect: org.hibernate.dialect.PostgreSQLDialect
default_schema: dts
jdbc.lob.non_contextual_creation: true
hibernate.ddl-auto: none
Expand Down
31 changes: 16 additions & 15 deletions src/test/java/com/device/CountryStorageTest.java
Original file line number Diff line number Diff line change
@@ -1,27 +1,26 @@
package com.device;

import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializationFeature;
import com.springpageable.storage.CountryStorage;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.Mockito;
import org.mockito.junit.jupiter.MockitoExtension;

import java.io.IOException;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.util.List;

import static com.device.mock.Constants.*;
import static com.springpageable.storage.CountryStorage.*;
import static org.junit.jupiter.api.Assertions.*;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.when;
import static com.device.mock.Constants.ALL_COUNTRIES_SIZE;
import static com.device.mock.Constants.COUNTRY_BG_KEY;
import static com.device.mock.Constants.COUNTRY_BG_VALUE;
import static com.device.mock.Constants.COUNTRY_DE_KEY;
import static com.device.mock.Constants.COUNTRY_DE_VALUE;
import static com.device.mock.Constants.EMPTY_STRING;
import static com.device.mock.Constants.NON_EXISTING_COUNTRY_CODE;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertNull;
import static org.junit.jupiter.api.Assertions.assertTrue;

@ExtendWith(MockitoExtension.class)
class CountryStorageTest {
Expand All @@ -42,7 +41,8 @@ void testThat_getCountriesByCountryCodes_returnsTheExpectedCountries() {
assertFalse(result.isEmpty());
assertEquals(2, result.size());
assertEquals(2, result.stream()
.filter(country -> country.getName().equals(COUNTRY_BG_VALUE) || country.getName().equals(COUNTRY_DE_VALUE))
.filter(country -> country.getName().equals(COUNTRY_BG_VALUE) || country.getName()
.equals(COUNTRY_DE_VALUE))
.count());
}

Expand Down Expand Up @@ -107,7 +107,8 @@ void testThat_areAllCountryCodesSupported_validatesProperly() {
// Act & Assert
assertTrue(countryStorage.areAllCountryCodesSupported(List.of(COUNTRY_BG_KEY, COUNTRY_DE_KEY)));
assertFalse(
countryStorage.areAllCountryCodesSupported(List.of(COUNTRY_BG_KEY, NON_EXISTING_COUNTRY_CODE, COUNTRY_DE_KEY)));
countryStorage.areAllCountryCodesSupported(
List.of(COUNTRY_BG_KEY, NON_EXISTING_COUNTRY_CODE, COUNTRY_DE_KEY)));
}

@Test
Expand Down
Loading

0 comments on commit 1754eb7

Please sign in to comment.