Spring + Kotlin + itextpdf - spring

package com.cmn.dt.event.service
import com.cmn.dt.event.dto.EventResponseDto
import com.itextpdf.text.pdf.*
import com.itextpdf.text.pdf.PdfWriter
import com.itextpdf.text.pdf.PdfReader
import com.itextpdf.text.pdf.PdfImportedPage
import org.springframework.stereotype.Service
import java.io.ByteArrayInputStream
import java.io.ByteArrayOutputStream
#Service
class GenerateFDS {
fun eventReport(event: EventResponseDto): ByteArrayInputStream {
val document = Document()
val out = ByteArrayOutputStream()
PdfWriter.getInstance(document, out)
document.open()
val reader = PdfReader("FDS.pdf")
PdfWriter.getImportedPage(reader,1)
I have this error
GenerateFDS.kt: (40, 38): Unresolved reference: getImportedPage
I don't understand why. Thanks for your help.

fun eventReport(event: EventResponseDto): ByteArrayInputStream {
val document = Document()
val out = ByteArrayOutputStream()
val writer = PdfWriter.getInstance(document, out)
document.open()
val reader = PdfReader("FDS.pdf")
val imported = writer.getImportedPage(reader,1)
That's ok with this code.

Related

How to find interfaces with specified annotation using ClassPathScanningCandidateComponentProvider?

I try to replace org.reflections:reflections library by org.springframework:spring-context and ClassPathScanningCandidateComponentProvider because reflections library has a bug that is critical for me.
I want to find all classes, interfaces and subclasses that contain specific annotation. It was easy in reflections:
var subTypeScanner = new SubTypesScanner( true );
var typeAnnotationScanner = new TypeAnnotationsScanner();
var configBuilder = new ConfigurationBuilder().setUrls( ClasspathHelper.forPackage( "com.example" ) )
.setScanners( subTypeScanner, typeAnnotationScanner );
Reflections reflections= new Reflections( configBuilder );
Set<Class<?>> typesAnnotatedWith = reflections.getTypesAnnotatedWith(Path.class);
Unfortunately it is not easy for me using ClassPathScanningCandidateComponentProvider. I have made workaround to find subclasses. First I find set of classes and after that I search for subclasses separately for each class:
protected Set< Class< ? > > findClasses( ClassPathScanningCandidateComponentProvider aScanner )
{
return aScanner.findCandidateComponents( "com.example" )
.stream()
.map( BeanDefinition::getBeanClassName )
.map( e -> {
try
{
return Class.forName( e );
}
catch( ClassNotFoundException aE )
{
throw new RuntimeException( aE );
}
} )
.collect( Collectors.toUnmodifiableSet() );
}
protected Set< Class< ? > > findSubClasses( Set< Class< ? > > aClasses )
{
return aClasses.stream()
.map( aClass -> {
ClassPathScanningCandidateComponentProvider scanner =
new ClassPathScanningCandidateComponentProvider( false );
scanner.addIncludeFilter( new AssignableTypeFilter( aClass ) );
return scanner.findCandidateComponents( "com.example" );
} )
.flatMap(Collection::stream)
.map( BeanDefinition::getBeanClassName )
.map( e -> {
try
{
return Class.forName( e );
}
catch( ClassNotFoundException aE )
{
throw new RuntimeException( aE );
}
} )
.collect( Collectors.toUnmodifiableSet() );
}
Unfortunately I do not know how to find interfaces with annotation. Following code does not finds interfaces. It only finds classes that has Path annotation or extends class or implements interface that has Path annotation.
ClassPathScanningCandidateComponentProvider scanner =
new ClassPathScanningCandidateComponentProvider( false );
scanner.addIncludeFilter( new AnnotationTypeFilter( Path.class, true, true ) );
Set<Class<?>> classes = findClasses(scanner);
//findSubClasses(findClasses( scanner ))
How to find interfaces with Path annotation?
The current implementation is also clear as mentioned on method level docs
The default implementation checks whether the class is not an interface and not dependent on an enclosing class.
As per this github issue we can fix this by overridden behaviour of isCandidateComponent() method
Here is the sample reference working code from the github issue
provider = new ClassPathScanningCandidateComponentProvider(false) {
#Override
protected boolean isCandidateComponent(AnnotatedBeanDefinition beanDefinition) {
return super.isCandidateComponent(beanDefinition) || beanDefinition.getMetadata().isAbstract();
}
};
Annotation
package in.silentsudo.classpathannotationscanner.annotation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
#Retention(value = RetentionPolicy.RUNTIME)
#Target(ElementType.TYPE)
public #interface MyCustomAnnotation {
}
My Simple interface
package in.silentsudo.classpathannotationscanner;
import in.silentsudo.classpathannotationscanner.annotation.MyCustomAnnotation;
#MyCustomAnnotation
public interface SimpleInterface {
}
Sample test code
package in.silentsudo.classpathannotationscanner;
import in.silentsudo.classpathannotationscanner.annotation.MyCustomAnnotation;
import org.springframework.beans.factory.annotation.AnnotatedBeanDefinition;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.context.annotation.ClassPathScanningCandidateComponentProvider;
import org.springframework.core.type.filter.AnnotationTypeFilter;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Set;
#RestController
#RequestMapping("/list")
public class SampleController {
private final ClassPathScanningCandidateComponentProvider provider;
SampleController() {
provider = new ClassPathScanningCandidateComponentProvider(false) {
#Override
protected boolean isCandidateComponent(AnnotatedBeanDefinition beanDefinition) {
return super.isCandidateComponent(beanDefinition) || beanDefinition.getMetadata().isAbstract();
}
};
provider.addIncludeFilter(new AnnotationTypeFilter(MyCustomAnnotation.class, true, true));
}
#GetMapping
public Map<String, Object> list() throws Exception {
return Map.of("mycustomannotationsmarkedinterafce", getData());
}
public List<String> getData() throws Exception {
final Set<BeanDefinition> classes = provider.findCandidateComponents("in.silentsudo.classpathannotationscanner");
List<String> names = new ArrayList<>();
for (BeanDefinition bean : classes) {
Class<?> clazz = Class.forName(bean.getBeanClassName());
names.add(clazz.getName());
}
return names;
}
}
This should give following response:
{
"mycustomannotationsmarkedinterafce": [
"in.silentsudo.classpathannotationscanner.SimpleInterface"
]
}

Spring boot autowired service all variable fields are null

Currently I have many spring boot services annotated with #Service.
Every service works fine and there are no problems. There is a problem only with LogsService. LogsService is implemented the same way as other services such as StaticPageService.
In order to log in a user must use method "onLoginClicked" from LoginViewModel class.
LoginViewModel class:
package com.flyingdynamite.jvmbackend.views.login
import com.flyingdynamite.jvmbackend.data.constants.LogCategory
import com.flyingdynamite.jvmbackend.extensions.isNotNull
import com.flyingdynamite.jvmbackend.security.hash.Hash
import com.flyingdynamite.jvmbackend.service.AdminAuthorizationService
import com.flyingdynamite.jvmbackend.service.LogsService
import com.flyingdynamite.jvmbackend.service.UserModelService
import com.flyingdynamite.jvmbackend.util.SystemInformation
import com.flyingdynamite.jvmbackend.util.base.validation.Argon2PasswordHash
import com.flyingdynamite.jvmbackend.util.generator.ApiAccessCredentialsGenerator
import com.flyingdynamite.jvmbackend.util.generator.TextIdGenerator
import com.flyingdynamite.jvmbackend.views.AdminPanelRoute
import com.flyingdynamite.jvmbackend.views.BaseViewModel
import com.flyingdynamite.jvmbackend.views.dashboard.DashboardView
import kotlinx.coroutines.*
import org.apache.commons.lang3.RandomStringUtils
import org.apache.commons.lang3.time.StopWatch
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.beans.factory.config.ConfigurableBeanFactory
import org.springframework.context.annotation.Scope
import org.springframework.stereotype.Component
import org.springframework.util.unit.DataSize
import java.time.LocalDateTime
import java.time.ZoneId
import java.util.*
import java.util.concurrent.TimeUnit
#Component
#Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
open class LoginViewModel : BaseViewModel() {
private val TAG = "LoginViewModel"
#Autowired(required = true)
private lateinit var logsService: LogsService
#Autowired(required = true)
private lateinit var userModelService: UserModelService
#Autowired(required = true)
private lateinit var adminAuthorizationService: AdminAuthorizationService
private var loginJob: Job? = null
private var developerTasksJob: Job? = null
init {
viewModelScope.launch {
setPageTitle("Login")
}
}
override fun onCleared() {
loginJob?.cancel("onCleared")
developerTasksJob?.cancel("onCleared")
}
// #PostConstruct
private fun runScheduledDeveloperTask() {
logger.warn("runScheduledDeveloperTask")
if (productionMode) {
return
}
Timer(TAG).schedule(object : TimerTask() {
override fun run() {
logger.warn("runDeveloperComputationTask")
runDeveloperComputationTask()
}
}, Date.from(LocalDateTime.now().plusHours(6).atZone(ZoneId.systemDefault()).toInstant()))
}
fun onLoginClicked(login: String?, password: String?) {
if (loginJob != null) {
return
}
loginJob = computationViewModelScope.launch {
logsService.enqueue(TAG, LogCategory.INFO, "onLoginClicked / login _> ${login} / password _> ${password}")
println("$TAG -> onLoginClicked / login _> ${login} / password _> ${password}")
setIsLoading(true)
setErrorText(null)
val inputValuesErrorText = inputValuesErrorText(login, password)
println("$TAG -> onLoginClicked / inputValuesErrorText _> ${inputValuesErrorText} / isEmpty _> ${inputValuesErrorText.isEmpty()}")
val authorized = adminAuthorizationService.isAuthorized(login!!, password!!)
val resultError = if (inputValuesErrorText.isNotNull()) {
inputValuesErrorText
} else {
if (authorized) null else localizedTexts.getOrEmpty("wrong_credentials")
}
println("$TAG -> onLoginClicked / resultError _> ${resultError}")
setErrorText(resultError)
delay(100)
setIsLoading(false)
setNavigateTo(
if (authorized) Triple(
DashboardView::class.java,
AdminPanelRoute.DASHBOARD,
userModelService.getUserByUsernameOrThrow(login)
) else null
)
loginJob = null
// setNavigateTo(null)
}
}
userModelService and adminAuthorizationService have no issues.
LogsService class:
package com.flyingdynamite.jvmbackend.service
import com.flyingdynamite.jvmbackend.data.constants.LogCategory
import com.flyingdynamite.jvmbackend.data.model.LogItem
import com.flyingdynamite.jvmbackend.repository.jpa.LogsJpaRepository
import com.flyingdynamite.jvmbackend.service.exception.NotFoundException
import com.flyingdynamite.jvmbackend.util.AppInfo
import com.flyingdynamite.jvmbackend.util.LogUtils
import com.vaadin.flow.server.WebBrowser
import kotlinx.coroutines.*
import org.slf4j.Logger
import org.slf4j.LoggerFactory
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.beans.factory.config.ConfigurableBeanFactory
import org.springframework.context.annotation.Scope
import org.springframework.data.repository.findByIdOrNull
import org.springframework.stereotype.Service
import org.springframework.transaction.annotation.Transactional
import java.time.Duration
import java.time.Instant
import java.time.LocalDateTime
import java.time.ZoneId
import java.util.*
import java.util.concurrent.TimeUnit
import javax.persistence.EntityManager
import javax.persistence.EntityManagerFactory
import javax.persistence.PersistenceContext
import javax.persistence.criteria.CriteriaBuilder
import javax.persistence.criteria.Predicate
import java.util.concurrent.ConcurrentLinkedQueue
#Service
#Scope(ConfigurableBeanFactory.SCOPE_SINGLETON)
open class LogsService : BaseService() {
private val TAG = "LogService"
#Autowired(required = true)
private lateinit var appInfo: AppInfo
#Autowired(required = true)
private lateinit var logRepository: LogsJpaRepository
#Autowired(required = true)
#PersistenceContext
private lateinit var entityManager: EntityManager
#Autowired(required = true)
private lateinit var entityManagerFactory: EntityManagerFactory
private val queue: ConcurrentLinkedQueue<LogItem> = ConcurrentLinkedQueue<LogItem>()
private val cmdLog: Logger by lazy {
LoggerFactory.getLogger(this::class.java)
}
// private val queue: ConcurrentLinkedQueue<LogItem> by lazy {
// ConcurrentLinkedQueue<LogItem>()
// }
private var deleteOlderDays: Int = 30
private var insertMinLogItemsCount: Int = 100
private var insertMaxSecondsDifference: Int = 5
private var isActive: Boolean = true
private var queueJob: Job? = null
private val terminatingMessage: String = "TERMINATING !"
private var displayInCMD: Boolean = true
#Synchronized
fun enableDisplayInCMD() {
displayInCMD = true
if (isActive) {
startQueueThreadIfPossible()
}
}
#Synchronized
fun disableDisplayInCMD() {
displayInCMD = false
if (isActive) {
startQueueThreadIfPossible()
}
}
#Synchronized
fun activate() {
if (displayInCMD) {
cmdLog.trace("activating _ !")
}
isActive = true
startQueueThreadIfPossible()
}
#Synchronized
fun deactivate() {
if (displayInCMD) {
cmdLog.warn("DEACTIVATED _ ! ")
}
onCleared()
}
#Synchronized
fun onCleared() {
isActive = false
queueJob?.cancel("onCleared")
queueJob = serviceScope.launch {
delay(TimeUnit.SECONDS.toMillis(2))
if (displayInCMD) {
cmdLog.warn("inserting queue all remaining items -> ${queue.size} ")
}
insertList(queue.toList())
queue.clear()
queueJob = null
}
}
}
BaseService class:
package com.flyingdynamite.jvmbackend.service
import com.flyingdynamite.jvmbackend.extensions.FD_CPU_INTENSIVE_LIMITLESS
import com.flyingdynamite.jvmbackend.extensions.FD_IO_LIMITLESS
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
open class BaseService() {
protected val serviceScope:CoroutineScope = CoroutineScope(Dispatchers.FD_IO_LIMITLESS)
protected val serviceComputationScope: CoroutineScope = CoroutineScope(Dispatchers.FD_CPU_INTENSIVE_LIMITLESS)
protected fun onException(e: Exception) {
println("[Exception] -> ${e.message}")
e.printStackTrace()
}
}
When user executes onLoginClicked this error occurs:
Exception in thread "pool-2-thread-1" java.lang.NullPointerException
at com.flyingdynamite.jvmbackend.service.LogsService.enqueue(LogsService.kt:171)
at com.flyingdynamite.jvmbackend.service.LogsService.enqueue(LogsService.kt:162)
at com.flyingdynamite.jvmbackend.views.login.LoginViewModel$onLoginClicked$1.invokeSuspend(LoginViewModel.kt:81)
at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:33)
LogsService.kt:171 -> queue.offer(log) in method enqueue from LogsService class.
If I change the code and execute for example something with serviceScope variable the same error occurs. Then serviceScope is null. Every variable inside LogsService is null when I am trying to use it.
Please help mi with this. Where is the mistake ?
Resolved !
Issue was in the logs but only when logging.level.root=DEBUG was set in application.properties
After long search this was somewhere in the logs:
cannot get proxied via CGLIB: Calls to this method will NOT be routed to the >target instance and might lead to NPEs against uninitialized fields in the >proxy instance.
After adding "open" to every method of LogsService class - log entry is not displaying anymore. Seems it is fixed. Not getting any NPE anymore.

page.nextPageable() endless loop in spring boot

in the code below data is fetched for the first three days without problems, at day 4 the code in the while loop that should fetch the next page always starts with day 2022-03-01 and loops. why?
the code should fetch one page from the database, do a computation on the elements for day 1. In the next run it checks if the already fetched data does contain all data for the new day otherwise it gets new data from the database.
But after day3 the while loop in getMoreData gets stuck and runs in definitively, apparently one java process keeps one core busy all the time even after aborting the eclipse test.
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.util.ArrayList;
import java.util.List;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
import org.springframework.stereotype.Component;
import com.javainuse.data.EmployeeRepository;
import com.javainuse.model.Employee;
#Component
public class EmployeeController {
#Autowired
private EmployeeRepository employeeData;
private Pageable pageable = PageRequest.of(0, 2);
public void compareData(LocalDateTime start, LocalDateTime end) {
List<Employee> futureDays = new ArrayList<>();
Page<Employee> fromDB = employeeData.findAllByHiringDateGreaterThanEqual(
LocalDateTime.of(2022,3,1, 1,1), pageable);
boolean firstRun = true;
while (true) {
if (start.isAfter(end)) {
break;
}
LocalDateTime tmpEnd = start.plusHours(23).plusMinutes(59).plusSeconds(59);
// works, the commented lines go trough the data and fetch all 5 employees without problems
// Page<Employee> fromDB = employeeData.findAllByHiringDateGreaterThanEqual(
// LocalDateTime.of(2022,3,1, 1,1), pageable);
// while(fromDB.hasNext()) {
// pageable = fromDB.nextPageable();
// fromDB = employeeData.findAllByHiringDateGreaterThanEqual(
// LocalDateTime.of(2022,3,1, 1,1), pageable);
// System.out.println();
// }
List<Employee> employees = new ArrayList<Employee>();
List<Employee> currentDay = new ArrayList<>();
if (firstRun) {
employees.addAll(fromDB.toList());
firstRun = false;
}
if (futureDays.size() != 0) {
//currentDay.addAll(futureDays);
employees.addAll(futureDays);
futureDays = new ArrayList<>();
}
if (employees.size() != 0) {
if (!employees.get(employees.size()-1).getHiringDate().isAfter(tmpEnd)) {
getMoreData(employees, fromDB, start, tmpEnd);
}
}
for (Employee x: employees) {
if (x.getHiringDate().isEqual(start) || x.getHiringDate().isEqual(tmpEnd) ||
x.getHiringDate().isAfter(start) && x.getHiringDate().isBefore(tmpEnd)) {
currentDay.add(x);
}
else {
futureDays.add(x);
}
}
for (Employee x: currentDay) {
System.out.println(x.toString());
}
start = start.plusDays(1);
}
}
private void getMoreData(
List<Employee> data, Page<Employee> page, LocalDateTime start, LocalDateTime end) {
while (page.hasNext()) {
pageable = page.nextPageable();
data.addAll(employeeData.findAllByHiringDateGreaterThanEqual(
LocalDateTime.of(2022,3,1, 1,1), this.pageable).toList());
// stop when the last entry in a page is after the current day
if (data.get(data.size()-1).getHiringDate().isAfter(
LocalDateTime.of(start.toLocalDate(), LocalTime.of(23,59,59)))) {
break;
}
}
}
}
import static org.junit.jupiter.api.Assertions.assertEquals;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.List;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import com.javainuse.controllers.EmployeeController;
import com.javainuse.data.EmployeeRepository;
import com.javainuse.model.Employee;
import org.springframework.test.context.ActiveProfiles;
import org.springframework.test.context.junit.jupiter.SpringExtension;
#ExtendWith(SpringExtension.class)
#SpringBootTest
#ActiveProfiles("test")
public class MyTest {
#Autowired
EmployeeRepository repo;
#Autowired
EmployeeController controller;
#Test
public void testx() {
List<Employee> data = new ArrayList<>();
for (int n=1; n<=5; n++) {
Employee one = Employee.builder()
.dept(String.valueOf(n))
.name(String.valueOf(n))
.hiringDate(LocalDateTime.of(2022, 03, n, 10,00))
.build();
data.add(one);
}
repo.saveAll(data);
List<Employee> all = repo.findAll();
assertEquals(5, all.size());
controller.compareData(LocalDateTime.of(2022,3,1,00,00),LocalDateTime.of(2022,3,7,23,59,59));
}
}

Error while binding parameter from AWS Parameter Store in Spring Boot

Due to some know vulnerabilities, I can't use spring-cloud-starter-aws-parameter-store-config in my org. So I simply tried to extract code from jar and use it in my project. But I'm not able to bind parameter from aws store to spring boot. I'm getting following error-
Caused by: java.lang.IllegalArgumentException: Could not resolve placeholder 'db.username' in value "${db.username}"
at org.springframework.util.PropertyPlaceholderHelper.parseStringValue(PropertyPlaceholderHelper.java:178) ~[spring-core-5.2.0.RELEASE.jar:5.2.0.RELEASE]
at org.springframework.util.PropertyPlaceholderHelper.replacePlaceholders(PropertyPlaceholderHelper.java:124) ~[spring-core-5.2.0.RELEASE.jar:5.2.0.RELEASE]
at org.springframework.core.env.AbstractPropertyResolver.doResolvePlaceholders(AbstractPropertyResolver.java:236) ~[spring-core-5.2.0.RELEASE.jar:5.2.0.RELEASE]
at org.springframework.core.env.AbstractPropertyResolver.resolveRequiredPlaceholders(AbstractPropertyResolver.java:210) ~[spring-core-5.2.0.RELEASE.jar:5.2.0.RELEASE]
at org.springframework.context.support.PropertySourcesPlaceholderConfigurer.lambda$processProperties$0(PropertySourcesPlaceholderConfigurer.java:175) ~[spring-context-5.2.0.RELEASE.jar:5.2.0.RELEASE]
at org.springframework.context.support.PropertySourcesPlaceholderConfigurer$$Lambda$220/254979217.resolveStringValue(Unknown Source) ~[na:na]
at org.springframework.beans.factory.support.AbstractBeanFactory.resolveEmbeddedValue(AbstractBeanFactory.java:908) ~[spring-beans-5.2.0.RELEASE.jar:5.2.0.RELEASE]
at org.springframework.beans.factory.support.DefaultListableBeanFactory.doResolveDependency(DefaultListableBeanFactory.java:1228) ~[spring-beans-5.2.0.RELEASE.jar:5.2.0.RELEASE]
at org.springframework.beans.factory.support.DefaultListableBeanFactory.resolveDependency(DefaultListableBeanFactory.java:1207) ~[spring-beans-5.2.0.RELEASE.jar:5.2.0.RELEASE]
at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor$AutowiredFieldElement.inject(AutowiredAnnotationBeanPostProcessor.java:636) ~[spring-beans-5.2.0.RELEASE.jar:5.2.0.RELEASE]
at org.springframework.beans.factory.annotation.InjectionMetadata.inject(InjectionMetadata.java:116) ~[spring-beans-5.2.0.RELEASE.jar:5.2.0.RELEASE]
at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor.postProcessProperties(AutowiredAnnotationBeanPostProcessor.java:397) ~[spring-beans-5.2.0.RELEASE.jar:5.2.0.RELEASE]
... 37 common frames omitted
These are the files that I'm using
import com.amazonaws.services.simplesystemsmanagement.AWSSimpleSystemsManagement;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Configuration;
#Configuration
#EnableConfigurationProperties({AwsParamStoreProperties.class})
#ConditionalOnClass({AWSSimpleSystemsManagement.class, AwsParamStorePropertySourceLocator.class})
#ConditionalOnProperty(
prefix = "aws.paramstore",
name = {"enabled"},
matchIfMissing = true
)
public class AwsParamStoreBootstrapConfiguration {
}
import javax.validation.constraints.NotEmpty;
import javax.validation.constraints.NotNull;
import javax.validation.constraints.Pattern;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.validation.annotation.Validated;
#ConfigurationProperties("aws.paramstore")
#Validated
public class AwsParamStoreProperties {
public static final String CONFIG_PREFIX = "aws.paramstore";
#NotNull
#Pattern(
regexp = "(/[a-zA-Z0-9.\\-_]+)*"
)
private String prefix = "/config";
#NotEmpty
private String defaultContext = "application";
#NotNull
#Pattern(
regexp = "[a-zA-Z0-9.\\-_/]+"
)
private String profileSeparator = "_";
private boolean failFast = true;
private String name;
private boolean enabled = true;
public AwsParamStoreProperties() {
}
//Getter and Setters
}
import com.amazonaws.services.simplesystemsmanagement.AWSSimpleSystemsManagement;
import com.amazonaws.services.simplesystemsmanagement.model.GetParametersByPathRequest;
import com.amazonaws.services.simplesystemsmanagement.model.GetParametersByPathResult;
import com.amazonaws.services.simplesystemsmanagement.model.Parameter;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Set;
import org.springframework.core.env.EnumerablePropertySource;
public class AwsParamStorePropertySource extends EnumerablePropertySource<AWSSimpleSystemsManagement> {
private String context;
private Map<String, Object> properties = new LinkedHashMap();
public AwsParamStorePropertySource(String context, AWSSimpleSystemsManagement ssmClient) {
super(context, ssmClient);
this.context = context;
}
public void init() {
GetParametersByPathRequest paramsRequest = (new GetParametersByPathRequest()).withPath(this.context).withRecursive(true).withWithDecryption(true);
this.getParameters(paramsRequest);
}
public String[] getPropertyNames() {
Set<String> strings = this.properties.keySet();
return (String[])strings.toArray(new String[strings.size()]);
}
public Object getProperty(String name) {
return this.properties.get(name);
}
private void getParameters(GetParametersByPathRequest paramsRequest) {
GetParametersByPathResult paramsResult = ((AWSSimpleSystemsManagement)this.source).getParametersByPath(paramsRequest);
Iterator var3 = paramsResult.getParameters().iterator();
while(var3.hasNext()) {
Parameter parameter = (Parameter)var3.next();
String key = parameter.getName().replace(this.context, "").replace('/', '.');
this.properties.put(key, parameter.getValue());
}
if (paramsResult.getNextToken() != null) {
this.getParameters(paramsRequest.withNextToken(paramsResult.getNextToken()));
}
}
}
import com.amazonaws.services.simplesystemsmanagement.AWSSimpleSystemsManagement;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.cloud.bootstrap.config.PropertySourceLocator;
import org.springframework.core.env.CompositePropertySource;
import org.springframework.core.env.ConfigurableEnvironment;
import org.springframework.core.env.Environment;
import org.springframework.core.env.PropertySource;
import org.springframework.util.ReflectionUtils;
public class AwsParamStorePropertySourceLocator implements PropertySourceLocator {
private AWSSimpleSystemsManagement ssmClient;
private AwsParamStoreProperties properties;
private List<String> contexts = new ArrayList();
private Log logger = LogFactory.getLog(this.getClass());
public AwsParamStorePropertySourceLocator(AWSSimpleSystemsManagement ssmClient, AwsParamStoreProperties properties) {
this.ssmClient = ssmClient;
this.properties = properties;
}
public List<String> getContexts() {
return this.contexts;
}
public PropertySource<?> locate(Environment environment) {
if (!(environment instanceof ConfigurableEnvironment)) {
return null;
} else {
ConfigurableEnvironment env = (ConfigurableEnvironment)environment;
String appName = this.properties.getName();
if (appName == null) {
appName = env.getProperty("spring.application.name");
}
List<String> profiles = Arrays.asList(env.getActiveProfiles());
String prefix = this.properties.getPrefix();
String defaultContext = prefix + "/" + this.properties.getDefaultContext();
this.contexts.add(defaultContext + "/");
this.addProfiles(this.contexts, defaultContext, profiles);
String baseContext = prefix + "/" + appName;
this.contexts.add(baseContext + "/");
this.addProfiles(this.contexts, baseContext, profiles);
Collections.reverse(this.contexts);
CompositePropertySource composite = new CompositePropertySource("aws-param-store");
Iterator var9 = this.contexts.iterator();
while(var9.hasNext()) {
String propertySourceContext = (String)var9.next();
try {
composite.addPropertySource(this.create(propertySourceContext));
} catch (Exception var12) {
if (this.properties.isFailFast()) {
this.logger.error("Fail fast is set and there was an error reading configuration from AWS Parameter Store:\n" + var12.getMessage());
ReflectionUtils.rethrowRuntimeException(var12);
} else {
this.logger.warn("Unable to load AWS config from " + propertySourceContext, var12);
}
}
}
return composite;
}
}
private AwsParamStorePropertySource create(String context) {
AwsParamStorePropertySource propertySource = new AwsParamStorePropertySource(context, this.ssmClient);
propertySource.init();
return propertySource;
}
private void addProfiles(List<String> contexts, String baseContext, List<String> profiles) {
Iterator var4 = profiles.iterator();
while(var4.hasNext()) {
String profile = (String)var4.next();
contexts.add(baseContext + this.properties.getProfileSeparator() + profile + "/");
}
}
}
bootstrap.yml
aws:
paramstore:
prefix: /config
name: sample
enabled: true
profileSeparator: _
spring.factories
org.springframework.cloud.bootstrap.BootstrapConfiguration=\com.app.paramstore.AwsParamStoreBootstrapConfiguration

How to mock log4j2 logger object using slf4j in Junit

I have below code where I am creating different log4j2 loggers object using slf4j factory and I need to write to specific log file based on input param.
#Service
class WriterService {
val logger1: Logger = LoggerFactory.getLogger("t_logger")
val logger2: Logger = LoggerFactory.getLogger("b_logger")
val labelKey = "Label"
fun writeLog(payload: Map<String, Any>){
if(payload.containsKey(labelKey)) {
val label = payload[labelKey].toString().toLowerCase()
if (label == "t") {
logger1.info("{}", payload)
} else if (label == "b") {
logger2.info("{}", payload)
}
}
}
I am wondering how can I mock Logger objects using MockitoJUnitRunner so that I can mock which logger object was invoked ?
I found solution using Appender and ArgumentCaptor...
Solution below for someone who might be interested...
import org.apache.logging.log4j.Level
import org.apache.logging.log4j.LogManager
import org.apache.logging.log4j.core.Appender
import org.apache.logging.log4j.core.LogEvent
import org.apache.logging.log4j.core.Logger
import org.junit.After
import org.junit.Assert.assertEquals
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.ArgumentCaptor
import org.mockito.Captor
import org.mockito.InjectMocks
import org.mockito.Mock
import org.mockito.Mockito.*
import org.mockito.runners.MockitoJUnitRunner
import java.lang.StringBuilder
#RunWith(MockitoJUnitRunner::class)
class ServiceTest {
#InjectMocks
var service: WriterService
#Mock
private val mockAppender: Appender? = null
#Captor
private val captorLoggingEvent: ArgumentCaptor<LogEvent>? = null
private var logger1: Logger? = null
private var logger2: Logger? = null
#Before
fun setup() {
`when`(mockAppender!!.name).thenReturn("MockAppender")
`when`(mockAppender.isStarted).thenReturn(true)
`when`(mockAppender.isStopped).thenReturn(false)
logger1 = LogManager.getLogger("t_logger") as Logger
logger1!!.addAppender(mockAppender)
logger1!!.level = Level.INFO
logger2 = LogManager.getLogger("b_logger") as Logger
logger2!!.addAppender(mockAppender)
logger2!!.level = Level.INFO
}
#After
fun tearDown() {
logger1!!.removeAppender(mockAppender!!)
logger2!!.removeAppender(mockAppender!!)
}
#Test
fun writeLog_shouldWriteTransactionLogWhenLabelIs_T() {
val payload = hashMapOf("Label" to "t")
service.writeLog(payload)
verify(mockAppender, times(1))!!.append(captorLoggingEvent!!.capture())
assertEquals(captorLoggingEvent.value.level, Level.INFO)
assertEquals(captorLoggingEvent.value.loggerName, "t_logger")
assertEquals(captorLoggingEvent.value.message.formattedMessage, "{Label=t}")
}
#Test
fun writeLog_shouldWriteBackendLogWhenLabelIs_B() {
val payload = hashMapOf("Label" to "b")
service.writeLog(payload)
verify(mockAppender, times(1))!!.append(captorLoggingEvent!!.capture())
assertEquals(captorLoggingEvent.value.level, Level.INFO)
assertEquals(captorLoggingEvent.value.loggerName, "b_logger")
assertEquals(captorLoggingEvent.value.message.formattedMessage, "{Label=b}")
}
}

Resources