NullPointerException by testing a Service with mocked JPARepository - spring

I have a ServiceImp where the repository and objectmapper are injected.
When performing the tests I get a nullpointexception.
I believe the Mock is working because when I print the result Candidate createdOne = candidateRepository.save(c1) I get back c1.
#Service
#RequiredArgsConstructor(onConstructor = #__(#Autowired))
public class CandidateServiceImp implements CandidateService {
private final CandidateRepository candidateRepository;
private final ObjectMapper objectMapper;
#Override
public List<CandidateDto> getAllCandidates() {
List<Candidate> candidateList = candidateRepository.findAll();
return candidateList.stream()
.map(Candidate::convertEntityToDto)
.collect(Collectors.toList());
}
#Override
public String findCandidateByIdNormal(Long id) throws JsonProcessingException {
Candidate foundCandidate = candidateRepository.findById(id).orElseThrow(() -> new CandidateNotFoundException(id));
return objectMapper.writerWithView(CandidateViews.Normal.class).writeValueAsString(foundCandidate.convertEntityToDto());
}
#Override
public String findCandidateByIdHr(Long id) throws JsonProcessingException {
Candidate foundCandidate = candidateRepository.findById(id).orElseThrow(() -> new CandidateNotFoundException(id));
return objectMapper.writerWithView(CandidateViews.Hr.class).writeValueAsString(foundCandidate.convertEntityToDto());
}
#Override
public CandidateDto createCandidate(CandidateDto candidateToCreateDto) {
Candidate candidateToCreate = candidateToCreateDto.convertDtoToEntity();
Candidate createdCandidate = candidateRepository.save(candidateToCreate);
return createdCandidate.convertEntityToDto();
}
And this ist my test:
#ExtendWith(SpringExtension.class)
public class CandidateServiceTest {
#InjectMocks
CandidateServiceImp candidateServiceImp;
#Mock
CandidateRepository candidateRepository;
private CandidateDto c1Dto;
private CandidateDto c2Dto;
private List<CandidateDto> candidateDtoList;
private Candidate c1;
private Candidate c2;
private List<Candidate> candidateList;
#BeforeEach
void setUp() {
c1Dto = new CandidateDto("Peter", "Parker", "pp#gmail.com", 3500L);
c2Dto = new CandidateDto("Mary", "Jane", "mj#gmail.com", 4500L);
candidateDtoList = List.of(c1Dto, c2Dto);
c1 = new Candidate("Peter", "Parker", "pp#gmail.com", 3500L);
c2 = new Candidate("Mary", "Jane", "mj#gmail.com", 4500L);
candidateList = List.of(c1, c2);
}
#Test
public void createCandidateShouldReturnCandidateDto() {
Mockito.doReturn(c1).when(candidateRepository).save(c1);
Candidate createdOne = candidateRepository.save(c1);
System.out.println();
System.out.println(createdOne.convertEntityToDto());
System.out.println();
assertEquals(c1Dto, candidateServiceImp.createCandidate(c1Dto));
}
And this is the message :
java.lang.NullPointerException: Cannot invoke "de.evoila.personalAbteilung.models.Candidate.convertEntityToDto()" because "createdCandidate" is null
Could someone tell me why it is not finding the createdCandidate?Thanks =D

As explained in the comments, one solution would be to add #EqualsAndHashCode to candidate.
I also find other solution :
#Test
public void createCandidateShouldReturnCandidateDto() {
Mockito.when(candidateRepository.save(any(Candidate.class))).thenAnswer(AdditionalAnswers.returnsFirstArg());
assertEquals(c1Dto, candidateServiceImp.createCandidate(c1Dto));
}
I mocked the repository with any(Candidate.class) and it also works.

Related

Entity listener can inject other Spring dependencies but not repository

I have this entity listener class:
#Component
public class AssignmentListener {
private KafkaService kafkaService;
private String topic;
private AssignmentMapper assignmentMapper;
private AttachmentRepository attachmentRepository;
#Autowired
public final void setKafkaService(KafkaService kafkaService) {
this.kafkaService = kafkaService;
}
#Autowired
public final void setTopic(
#Value("${topic}") String topic
) {
this.topic = topic;
}
#Autowired
public final void setAssignmentMapper(AssignmentMapper assignmentMapper) {
this.assignmentMapper = assignmentMapper;
}
#Autowired
public final void setAttachmentRepository(AttachmentRepository attachmentRepository) {
this.attachmentRepository = attachmentRepository;
}
#PostPersist
#PostUpdate
#Transactional("transactionManager")
#TransactionalEventListener(phase = TransactionPhase.BEFORE_COMMIT)
public void postUpdate(Assignment assignment) {
var attachments = attachmentRepository.findAllByAssignmentId(assignment.getId());
var dto = assignmentMapper.mapToKafkaMessage(assignment);
dto.setAttachments(
attachments.stream()
.map(Attachment::getPath)
.collect(Collectors.toSet())
);
kafkaService.sendMessage(
topic,
dto
);
}
}
and it worked normally until adding this last field which is repository. All other dependencies were injected however no matter what I do this won't get injected. Just to mention this is happening in tests. Do you have any suggestion?

How to test GET request with body in Spring RestController?

I have a rest controller like this;
#RestController
#RequiredArgsConstructor
#RequestMapping(PO)
public class PoController {
private final PoService service;
#GetMapping(value = FILTER, produces = APPLICATION_JSON_VALUE)
public ResponseEntity<List<PoDTO>> filter(PoFilterCriteria poFilterCriteria) {
return ok().body(service.getPos(poFilterCriteria));
}
}
And I want to write an unit test for it but I couldn't achieve to mock the service to return list.
This is my poFilterCriteria model;
#Data
public class PoFilterCriteria {
private double hp;
private FilterOperationType hpOperationType;
private double attack;
private FilterOperationType attackOperationType;
private double defense;
private FilterOperationType defenseOperationType;
}
And this is my test;
#WebMvcTest(value = PoController.class)
class PoControllerTest {
#Autowired
private MockMvc mockMvc;
#MockBean
private PoService service;
private PoDTO poDTO;
private List<PoDTO> poDTOList;
#BeforeEach
void setUp() {
poDTOList = new ArrayList<>();
poDTO = new Po();
poDTOList.add(poDTO);
}
#Test
public void filter_success() throws Exception {
PoFilterCriteria poFilterCriteria= new PoFilterCriteria ();
poFilterCriteria.setAttack(40);
poFilterCriteria.setAttackOperationType(GT);
poFilterCriteria.setHp(49);
poFilterCriteria.setHpOperationType(EQ);
poFilterCriteria.setDefense(60);
poFilterCriteria.setDefenseOperationType(LT);
when(service.getPos(poFilterCriteria)).thenReturn(poDTOList);
mockMvc.perform(get(PO + FILTER)
.param("hp", String.valueOf(40))
.param("hpOperationType", String.valueOf(GT))
.param("attack", String.valueOf(49))
.param("attackOperationType", String.valueOf(EQ))
.param("defense", String.valueOf(60))
.param("defenseOperationType", String.valueOf(LT))
.contentType(APPLICATION_JSON))
.andExpect(status().isOk())
.andExpect(content().json(new ObjectMapper().writeValueAsString(poDTOList)));
}
}
But the list that should return with size of 1 is returning empty.
What did I do wrong?
org.mockito.ArgumentMatchers#any(java.lang.Class)
when(service.getPos(any(PoFilterCriteria.class))).thenReturn(poDTOList);
or
org.mockito.ArgumentMatchers#same
when(service.getPos(same(poFilterCriteria))).thenReturn(poDTOList);

Mocking an autowired object of base abstract class

I'm writing the Junit test case for a class which is extended by an abstract class. This base abstract class has an autowired object of a different class which is being used in the class I'm testing.
I'm trying to mock in the subclass, but the mocked object is throwing a NullPointerException.
Example:
// class I am testing
public class GetTransactionsForProcessorGroupActivity extends GetTransactionsBaseActivity {
private static final Logger log = LogManager.getLogger(GetTransactionsForProcessorGroupActivity.class);
public GetTransactionsForProcessorGroupActivity(ARHFactory arhFactory, MetricsFactory metricsFactory) {
super(arhFactory, metricsFactory);
}
#Override
protected List<OverseasTransaction> getOverseasTransactions(Document herdDocument)
throws IllegalDateFormatException, ProcessorConfigurationException, DocumentException {
final String paymentProcessorGroup = HerdDocumentUtils.getPaymentProcessor(herdDocument);
final Date runDate = HerdDocumentUtils.getRunDate(herdDocument);
final List<String> paymentProcessorList = ProcessorGroupLookup.getProcessorsFromGroup(paymentProcessorGroup);
List<OverseasTransaction> overseasTransactionList = new ArrayList<OverseasTransaction>();
List<ProcessorTransactionWindow> processingWindows = new ArrayList<ProcessorTransactionWindow>();
for (final String processor : paymentProcessorList) {
ProcessorTransactionWindow transactionWindow = ProcessorCalendarUtils.getProcessorTransactionWindow(processor, runDate);
processingWindows.add(transactionWindow);
final Date processingFromDate = transactionWindow.getFromDate();
final Date processingToDate = transactionWindow.getToDate();
//NullpointerException on this line, as OverseasTransactionStore mock object returns null.
final List<OverseasTransaction> transactions = overseasTransactionsStore
.queryOverseasTransactionsOnPPTimelineandDates(processor, processingFromDate, processingToDate);
overseasTransactionList.addAll(transactions);
}
HerdDocumentUtils.putProcessingWindowDetails(herdDocument, processingWindows);
return overseasTransactionList;
}
}
// Base class
public abstract class GetTransactionsBaseActivity extends CoralHerdActivity implements ActionRequestHandler {
private static final Logger log = LogManager.getLogger(GetTransactionsBaseActivity.class);
#SuppressWarnings("unchecked")
private static final Map<String, String> S3_CONFIGURATION = AppConfig.findMap(Constants.S3_CONFIGURATION_KEY);
private static final String S3_BUCKET = S3_CONFIGURATION.get(Constants.BUCKET_NAME);
private static final class Status {
private static final String PROCESSOR_DETAILS_NOT_FOUND = "NoPaymentProcessorDetailsPresent";
private static final String TRANSACTIONS_OBTAINED = "TransactionsObtained";
private static final String NO_TRANSACTIONS_TO_BE_CONSIDERED = "NoTransactionsToBeConsidered";
private static final String NEGATIVE_OR_ZERO_AMOUNT = "NegativeOrZeroAmount";
}
protected final ARHFactory arhFactory;
protected final MetricsFactory metricsFactory;
#Autowired
OverseasTransactionsStore overseasTransactionsStore;
#Autowired
S3ClientProvider s3ClientProvider;
protected abstract List<OverseasTransaction> getOverseasTransactions(Document herdDocument)
throws IllegalDateFormatException, ProcessorConfigurationException, DocumentException;
#Override
public ActionResponse postActionRequest(final ActionRequest request) throws Exception {
TimedARH timedARH = (TimedARH) arhFactory.createARH();
timedARH.setHandler(this);
return timedARH.handle(request);
}
public ActionResponse handle(final ActionRequest request) throws Exception {
final Document herdDocument = HerdDocumentUtils.getFundFlowDocument(request);
final Metrics metrics = MetricsLogger.getMetrics(metricsFactory);
final String paymentProcessor = HerdDocumentUtils.getPaymentProcessor(herdDocument);
try {
final List<OverseasTransaction> overseasTransactionList;
MetricsLogger.logFundFlowExecution(metrics, paymentProcessor);
try {
overseasTransactionList = getOverseasTransactions(herdDocument);
} catch (ProcessorConfigurationException e) {
return new ActionComplete(Status.PROCESSOR_DETAILS_NOT_FOUND, herdDocument);
}
if (CollectionUtils.isEmpty(overseasTransactionList)) {
MetricsLogger.logTransactionMetrics(metrics, paymentProcessor, 0, BigDecimal.ZERO);
return new ActionComplete(Status.NO_TRANSACTIONS_TO_BE_CONSIDERED, herdDocument);
}
final String s3ObjectKey = getS3ObjectKey(request, paymentProcessor);
storeTransactionsInS3(overseasTransactionList, S3_BUCKET, s3ObjectKey);
final int itemCount = overseasTransactionList.size();
BigDecimal totalAmount = BigDecimal.ZERO;
for (OverseasTransaction overseasTransaction : overseasTransactionList) {
if (StringUtils.equalsIgnoreCase(overseasTransaction.getType(), Constants.TRANSACTION_TYPE_CHARGE)) {
totalAmount = totalAmount.add(overseasTransaction.getOverseasAmount());
} else if (StringUtils.equalsIgnoreCase(overseasTransaction.getType(), Constants.TRANSACTION_TYPE_REFUND)) {
totalAmount = totalAmount.subtract(overseasTransaction.getOverseasAmount());
}
}
MetricsLogger.logTransactionMetrics(metrics, paymentProcessor, itemCount, totalAmount);
HerdDocumentUtils.putS3Location(herdDocument, S3_BUCKET, s3ObjectKey);
HerdDocumentUtils.putTotalAmount(herdDocument, totalAmount);
HerdDocumentUtils.putTransactionItemCount(herdDocument, itemCount);
HerdDocumentUtils.putPaymentProcessor(herdDocument, paymentProcessor);
if (totalAmount.compareTo(BigDecimal.ZERO) <= 0) {
log.info("Total amount to disburse is zero or negative. {}", totalAmount);
return new ActionComplete(Status.NEGATIVE_OR_ZERO_AMOUNT, herdDocument);
}
return new ActionComplete(Status.TRANSACTIONS_OBTAINED, herdDocument);
} finally {
MetricsLogger.closeMetrics(metrics);
}
}
}
// Test class
#RunWith(PowerMockRunner.class)
#PowerMockIgnore({ "javax.management.*" })
#PrepareForTest({ HerdDocumentUtils.class, ProcessorGroupLookup.class })
public class GetTransactionsForProcessorGroupActivityTest extends AppConfigInitializedTestBase {
private static HerdInput herdInput;
private static HerdOutput herdOutput;
private static final String paymentProcessorGroup = "BillDesk";
private static final String paymentProcessorGroupNotFound = "IndiaPaymentGateway";
#Mock
ActionRequest request;
#Mock
WorkItemIdentifier workItemId;
#Mock
private CoralHerdActivity coralHerdActivity;
#Mock
Metrics metrics;
#Mock
Map<String, String> S3_CONFIGURATION_MAP;
#Mock
MetricsFactory metricsFactory;
#Mock
ARHFactory arhFactory;
#Mock
Document herdDocument;
#Mock
OverseasTransactionsStore overseasTransactionsStore;
#Mock
S3ClientProvider s3ClientProvider;
// #InjectMocks
// GetTransactionsForProcessorGroupActivity getTransactionsBaseActivityTest;
// new GetTransactionsForProcessorGroupActivity(arhFactory, metricsFactory);
#Before
public void setup() {
MockitoAnnotations.initMocks(this);
PowerMockito.mockStatic(HerdDocumentUtils.class);
// PowerMockito.mockStatic(ProcessorGroupLookup.class);
// GetTransactionsBaseActivity getTransactionsBaseActivity = new GetTransactionsBaseActivity(overseasTransactionsStore);
}
#Test
public void testEnact() {
GetTransactionsForProcessorGroupActivity getTransactionsForProcessorGroupActivity = Mockito
.mock(GetTransactionsForProcessorGroupActivity.class, Mockito.CALLS_REAL_METHODS);
Mockito.when(coralHerdActivity.enact(herdInput)).thenReturn(herdOutput);
final HerdOutput actualHerdOutput = getTransactionsForProcessorGroupActivity.enact(herdInput);
Assert.assertNotNull(actualHerdOutput);
}
#Test
public void testgetpaymentProcessorList() throws Exception {
Date date = new Date();
List<String> paymentProcessorList;
PowerMockito.when(HerdDocumentUtils.getPaymentProcessor(herdDocument)).thenReturn(paymentProcessorGroup);
PowerMockito.when(HerdDocumentUtils.getRunDate(herdDocument)).thenReturn(date);
paymentProcessorList = ProcessorGroupLookup.getProcessorsFromGroup(paymentProcessorGroup);
assertNotNull(paymentProcessorList);
}
#Test(expected = ProcessorConfigurationException.class)
public void testpaymentProcessorListNotFound() {
Date date = new Date();
PowerMockito.when(HerdDocumentUtils.getPaymentProcessor(herdDocument))
.thenReturn(paymentProcessorGroupNotFound);
PowerMockito.when(HerdDocumentUtils.getRunDate(herdDocument)).thenReturn(date);
List<String> paymentProcessorList = ProcessorGroupLookup.getProcessorsFromGroup(paymentProcessorGroupNotFound);
assertNotNull(paymentProcessorList);
}
#Test
public void canGetOverseasTransactiontest() throws Exception {
GetTransactionsForProcessorGroupActivity getTransactionsForProcessorGroupActivity = Mockito
.mock(GetTransactionsForProcessorGroupActivity.class);
// OverseasTransactionsStore overseasTransactionsStore =
// Mockito.mock(OverseasTransactionsStoreImpl.class);
Date date = new Date();
MockitoAnnotations.initMocks(this);
PowerMockito.when(HerdDocumentUtils.getPaymentProcessor(herdDocument)).thenReturn(paymentProcessorGroup);
PowerMockito.when(HerdDocumentUtils.getRunDate(herdDocument)).thenReturn(date);
// List<String> paymentProcessorList = new ArrayList<>();
// paymentProcessorList.add("BillDesk");
// PowerMockito.when(ProcessorGroupLookup.getProcessorsFromGroup(paymentProcessorGroup))
// .thenReturn(paymentProcessorList);
String fromDate = "Sat Mar 16 23:59:59 IST 2019";
String toDate = "Sat Mar 16 23:59:59 IST 2019";
DateFormat dateFormat = new SimpleDateFormat("EEE MMM dd HH:mm:ss zzz yyyy", Locale.ROOT);
List<OverseasTransaction> overseasTransactionList1 = createMockOverseasTransaction();
Mockito.doReturn(overseasTransactionList1).when(overseasTransactionsStore)
.queryOverseasTransactionsOnPPTimelineandDates(Mockito.isA(String.class), Mockito.isA(Date.class),
Mockito.isA(Date.class));
Mockito.when(getTransactionsForProcessorGroupActivity.getOverseasTransactions(herdDocument))
.thenCallRealMethod();
List<OverseasTransaction> overseasTransactionList = getTransactionsForProcessorGroupActivity
.getOverseasTransactions(herdDocument);
assertNotNull(overseasTransactionList);
// assertEquals(overseasTransactionList.getPaymentProcessorID(), actual);
}
private List<OverseasTransaction> createMockOverseasTransaction() {
Date date = new Date();
BigDecimal num = new BigDecimal(100001);
List<OverseasTransaction> overseasTransactionList = new ArrayList<OverseasTransaction>();
OverseasTransaction overseasTransaction = new OverseasTransaction();
overseasTransaction.setPurchaseID("purchaseID");
overseasTransaction.setSignatureID("signatureID");
overseasTransaction.setPaymentProcessorTransactionID("paymentProcessorTransactionID");
overseasTransaction.setType("Charge");
overseasTransaction.setSubType("subType");
overseasTransaction.setTransactionTimestamp(date);
overseasTransaction.setPaymentMethod("paymentMethod");
overseasTransaction.setTotalAmount(num);
overseasTransaction.setOverseasAmount(num);
overseasTransaction.setCurrency("currency");
overseasTransaction.setMarketplaceID("marketplaceID");
overseasTransaction.setOrderMetadata("orderMetadata");
overseasTransaction.setDisbursementID("disbursementID");
overseasTransaction.setReconState(1);
overseasTransaction.setPaymentProcessorID("BillDesk");
overseasTransaction.setRemittanceFileStatus("remittanceFileStatus");
overseasTransaction.setCrowID("crowID");
overseasTransaction.setSource("source");
overseasTransactionList.add(overseasTransaction);
return overseasTransactionList;
}
}
In my test file when I mock OverseasTransaction object, it gives me a NullPointerException. Do you have any suggestions about how we can mock this? All the above commented lines in my test indicates the things I tried but they still seem to throw the same error.
StackTrace of error: while executing canGetOverseasTransactiontest
N/A
java.lang.NullPointerException
at com.ingsfundflowservice.activity.GetTransactionsForProcessorGroupActivity.getOverseasTransactions(GetTransactionsForProcessorGroupActivity.java:71)
at com.ingsfundflowservice.activity.GetTransactionsForProcessorGroupActivityTest.canGetOverseasTransactiontest(GetTransactionsForProcessorGroupActivityTest.java:172)
at org.powermock.modules.junit4.internal.impl.PowerMockJUnit44RunnerDelegateImpl$PowerMockJUnit44MethodRunner.runTestMethod(PowerMockJUnit44RunnerDelegateImpl.java:310)
at org.powermock.modules.junit4.internal.impl.PowerMockJUnit44RunnerDelegateImpl$PowerMockJUnit44MethodRunner.executeTest(PowerMockJUnit44RunnerDelegateImpl.java:294)
at org.powermock.modules.junit4.internal.impl.PowerMockJUnit47RunnerDelegateImpl$PowerMockJUnit47MethodRunner.executeTestInSuper(PowerMockJUnit47RunnerDelegateImpl.java:127)
at org.powermock.modules.junit4.internal.impl.PowerMockJUnit47RunnerDelegateImpl$PowerMockJUnit47MethodRunner.executeTest(PowerMockJUnit47RunnerDelegateImpl.java:82)
at org.powermock.modules.junit4.internal.impl.PowerMockJUnit44RunnerDelegateImpl$PowerMockJUnit44MethodRunner.runBeforesThenTestThenAfters(PowerMockJUnit44RunnerDelegateImpl.java:282)
at org.powermock.modules.junit4.internal.impl.PowerMockJUnit44RunnerDelegateImpl.invokeTestMethod(PowerMockJUnit44RunnerDelegateImpl.java:207)
at org.powermock.modules.junit4.internal.impl.PowerMockJUnit44RunnerDelegateImpl.runMethods(PowerMockJUnit44RunnerDelegateImpl.java:146)
at org.powermock.modules.junit4.internal.impl.PowerMockJUnit44RunnerDelegateImpl$1.run(PowerMockJUnit44RunnerDelegateImpl.java:120)
at org.powermock.modules.junit4.internal.impl.PowerMockJUnit44RunnerDelegateImpl.run(PowerMockJUnit44RunnerDelegateImpl.java:122)
at org.powermock.modules.junit4.common.internal.impl.JUnit4TestSuiteChunkerImpl.run(JUnit4TestSuiteChunkerImpl.java:106)
at org.powermock.modules.junit4.common.internal.impl.AbstractCommonPowerMockRunner.run(AbstractCommonPowerMockRunner.java:53)
at org.powermock.modules.junit4.PowerMockRunner.run(PowerMockRunner.java:59)
You need to create object of Class which for which you are writing the test. i.e. GetTransactionsForProcessorGroupActivity. There is what you can do
#RunWith(MockitoJUnitRunner.class)
public class GetTransactionsForProcessorGroupActivityTest {
#InjectMocks
private GetTransactionsForProcessorGroupActivity getTransactionsForProcessorGroupActivity;
#Mock
private OverseasTransaction overseastransaction;
#Test
public void test() {
Mockito.when(overseastransaction.somemethod()).thenReturn(something);
}
}
This will make sure that getTransactionsForProcessorGroupActivity is created and overseastransaction is injected with mock object.
Please note the class annotation #RunWith. This make sure that all the properties are injected with mock object properly. Also, you can use Mockito instead of PowerMockito.
You can add a protected setter in the abstract class and then in your GetTransactionsForProcessorGroupActivity you can call the setter in the constructor.
Then you just use #Mock the field you need to set and then you either use #InjectMocks or more suggested to call the constructor in the #Setup method including the mock field to be set.

#RefreshScope annotated Bean registered through BeanDefinitionRegistryPostProcessor not getting refreshed on Cloud Config changes

I've a BeanDefinitionRegistryPostProcessor class that registers beans dynamically. Sometimes, the beans being registered have the Spring Cloud annotation #RefreshScope.
However, when the cloud configuration Environment is changed, such beans are not being refreshed. Upon debugging, the appropriate application events are triggered, however, the dynamic beans don't get reinstantiated. Need some help around this. Below is my code:
TestDynaProps:
public class TestDynaProps {
private String prop;
private String value;
public String getProp() {
return prop;
}
public void setProp(String prop) {
this.prop = prop;
}
public String getValue() {
return value;
}
public void setValue(String value) {
this.value = value;
}
#Override
public String toString() {
StringBuilder builder = new StringBuilder();
builder.append("TestDynaProps [prop=").append(prop).append(", value=").append(value).append("]");
return builder.toString();
}
}
TestDynaPropConsumer:
#RefreshScope
public class TestDynaPropConsumer {
private TestDynaProps props;
public void setProps(TestDynaProps props) {
this.props = props;
}
#PostConstruct
public void init() {
System.out.println("Init props : " + props);
}
public String getVal() {
return props.getValue();
}
}
BeanDefinitionRegistryPostProcessor:
public class PropertyBasedDynamicBeanDefinitionRegistrar implements BeanDefinitionRegistryPostProcessor, EnvironmentAware {
private ConfigurableEnvironment environment;
private final Class<?> propertyConfigurationClass;
private final String propertyBeanNamePrefix;
private final String propertyKeysPropertyName;
private Class<?> propertyConsumerBean;
private String consumerBeanNamePrefix;
private List<String> dynaBeans;
public PropertyBasedDynamicBeanDefinitionRegistrar(Class<?> propertyConfigurationClass,
String propertyBeanNamePrefix, String propertyKeysPropertyName) {
this.propertyConfigurationClass = propertyConfigurationClass;
this.propertyBeanNamePrefix = propertyBeanNamePrefix;
this.propertyKeysPropertyName = propertyKeysPropertyName;
dynaBeans = new ArrayList<>();
}
public void setPropertyConsumerBean(Class<?> propertyConsumerBean, String consumerBeanNamePrefix) {
this.propertyConsumerBean = propertyConsumerBean;
this.consumerBeanNamePrefix = consumerBeanNamePrefix;
}
#Override
public void setEnvironment(Environment environment) {
this.environment = (ConfigurableEnvironment) environment;
}
#Override
public void postProcessBeanFactory(ConfigurableListableBeanFactory arg0) throws BeansException {
}
#Override
public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry beanDefRegistry) throws BeansException {
if (environment == null) {
throw new BeanCreationException("Environment must be set to initialize dyna bean");
}
String[] keys = getPropertyKeys();
Map<String, String> propertyKeyBeanNameMapping = new HashMap<>();
for (String k : keys) {
String trimmedKey = k.trim();
String propBeanName = getPropertyBeanName(trimmedKey);
registerPropertyBean(beanDefRegistry, trimmedKey, propBeanName);
propertyKeyBeanNameMapping.put(trimmedKey, propBeanName);
}
if (propertyConsumerBean != null) {
String beanPropertyFieldName = getConsumerBeanPropertyVariable();
for (Map.Entry<String, String> prop : propertyKeyBeanNameMapping.entrySet()) {
registerConsumerBean(beanDefRegistry, prop.getKey(), prop.getValue(), beanPropertyFieldName);
}
}
}
private void registerConsumerBean(BeanDefinitionRegistry beanDefRegistry, String trimmedKey, String propBeanName, String beanPropertyFieldName) {
String consumerBeanName = getConsumerBeanName(trimmedKey);
AbstractBeanDefinition consumerDefinition = preparePropertyConsumerBeanDefinition(propBeanName, beanPropertyFieldName);
beanDefRegistry.registerBeanDefinition(consumerBeanName, consumerDefinition);
dynaBeans.add(consumerBeanName);
}
private void registerPropertyBean(BeanDefinitionRegistry beanDefRegistry, String trimmedKey, String propBeanName) {
AbstractBeanDefinition propertyBeanDefinition = preparePropertyBeanDefinition(trimmedKey);
beanDefRegistry.registerBeanDefinition(propBeanName, propertyBeanDefinition);
dynaBeans.add(propBeanName);
}
private String getConsumerBeanPropertyVariable() throws IllegalArgumentException {
Field[] beanFields = propertyConsumerBean.getDeclaredFields();
for (Field bField : beanFields) {
if (bField.getType().equals(propertyConfigurationClass)) {
return bField.getName();
}
}
throw new BeanCreationException(String.format("Could not find property of type %s in bean class %s",
propertyConfigurationClass.getName(), propertyConsumerBean.getName()));
}
private AbstractBeanDefinition preparePropertyBeanDefinition(String trimmedKey) {
BeanDefinitionBuilder bdb = BeanDefinitionBuilder.genericBeanDefinition(PropertiesConfigurationFactory.class);
bdb.addConstructorArgValue(propertyConfigurationClass);
bdb.addPropertyValue("propertySources", environment.getPropertySources());
bdb.addPropertyValue("conversionService", environment.getConversionService());
bdb.addPropertyValue("targetName", trimmedKey);
return bdb.getBeanDefinition();
}
private AbstractBeanDefinition preparePropertyConsumerBeanDefinition(String propBeanName, String beanPropertyFieldName) {
BeanDefinitionBuilder bdb = BeanDefinitionBuilder.genericBeanDefinition(propertyConsumerBean);
bdb.addPropertyReference(beanPropertyFieldName, propBeanName);
return bdb.getBeanDefinition();
}
private String getPropertyBeanName(String trimmedKey) {
return propertyBeanNamePrefix + trimmedKey.substring(0, 1).toUpperCase() + trimmedKey.substring(1);
}
private String getConsumerBeanName(String trimmedKey) {
return consumerBeanNamePrefix + trimmedKey.substring(0, 1).toUpperCase() + trimmedKey.substring(1);
}
private String[] getPropertyKeys() {
String keysProp = environment.getProperty(propertyKeysPropertyName);
return keysProp.split(",");
}
The Config class:
#Configuration
public class DynaPropsConfig {
#Bean
public PropertyBasedDynamicBeanDefinitionRegistrar dynaRegistrar() {
PropertyBasedDynamicBeanDefinitionRegistrar registrar = new PropertyBasedDynamicBeanDefinitionRegistrar(TestDynaProps.class, "testDynaProp", "dyna.props");
registrar.setPropertyConsumerBean(TestDynaPropConsumer.class, "testDynaPropsConsumer");
return registrar;
}
}
Application.java
#SpringBootApplication
#EnableDiscoveryClient
#EnableScheduling
public class Application extends SpringBootServletInitializer {
private static Class<Application> applicationClass = Application.class;
public static void main(String[] args) {
SpringApplication sa = new SpringApplication(applicationClass);
sa.run(args);
}
}
And, my bootstrap.properties:
spring.cloud.consul.enabled=true
spring.cloud.consul.config.enabled=true
spring.cloud.consul.config.format=PROPERTIES
spring.cloud.consul.config.watch.delay=15000
spring.cloud.discovery.client.health-indicator.enabled=false
spring.cloud.discovery.client.composite-indicator.enabled=false
application.properties
dyna.props=d1,d2
d1.prop=d1prop
d1.value=d1value
d2.prop=d2prop
d2.value=d2value
Here are some guesses:
1) Perhaps the #RefreshScope metadata is not being passed to your metadata for the bean definition. Call setScope()?
2) The RefreshScope is actually implemented by https://github.com/spring-cloud/spring-cloud-commons/blob/master/spring-cloud-context/src/main/java/org/springframework/cloud/context/scope/refresh/RefreshScope.java, which itself implements BeanDefinitionRegistryPostProcessor. Perhaps the ordering of these two post processors is issue.
Just guesses.
We finally resolved this by appending the #RefreshScope annotation on the proposed dynamic bean classes using ByteBuddy and then, adding them to Spring Context using Bean Definition Post Processor.
The Post Processor is added to spring.factories so that it loads before any other dynamic bean dependent beans.

Generate static map from database using a singleton class also using spring configuration #Autowired

I need to create an unmodifiable map generated from data obtained by querying a database. How, or can I, or is there a better way to do this using spring annotations?
I ran into a problem when creating a singleton for my Regions class and then trying to #Autowire in a RegionService to grab the object from the DAO. The problem is that spring can't instantiate the RegionService because it needs to instantiate the static singleton class Regions which needs to get data from the database as shown below in the constructor.
Please see me classes below (I've removed multiple unneeded methods that don't pertain to this question):
public final class Region {
private static final String DEFAULT_SEPERATOR = "-";
private final Integer key;
private final String description;
public Region(Integer pKey, String pDescription) {
this.key = pKey;
this.description = pDescription;
}
public Integer getKey() {
return this.key;
}
public String getValue() {
return this.description;
}
}
Here is my singleton:
public final class Regions {
private static Regions regionsInstance = null;
#Autowired
private RegionService regionService;
static Map<Integer, Region> regions;
private Regions() {
final Map<Integer, Region> tempRegions = new HashMap<Integer, Region>();
for (final Region region : this.regionService.retrieveAll()) {
tempRegions.put(region.getKey(), region);
}
regions = Collections.unmodifiableMap(tempRegions);
}
public static synchronized Regions getRegionsInstance() {
if (regionsInstance == null) {
regionsInstance = new Regions();
}
return regionsInstance;
}
public Region getRegion(final Integer pKey) {
return regions.get(pKey);
}
public List<Region> getRegions() {
return (List<Region>) regions.values();
}
}
My DAO and Service are just interfaces, no need to post those, here are my Impls:
#Service
public class RegionServiceImpl implements RegionService {
#Autowired
private RegionDAO regionDao;
#Override
public List<Region> retrieveAll() {
return this.regionDao.retrieveAll();
}
}
My DAOImpl (tested and works, just posting to give you the full picture):
#Repository
public class RegionDAOImpl implements RegionDAO {
private static final String SQL_RETRIEVE_REGIONS = "some random SQL";
#Autowired
private JdbcTemplate jdbcTemplate;
#Override
public List<Region> retrieveAll() {
try {
return this.jdbcTemplate.query(SQL_RETRIEVE_REGIONS, new ResultSetExtractor<List<Region>>() {
#Override
public List<Region> extractData(ResultSet rs) throws SQLException, DataAccessException {
return RegionDAOImpl.this.mapRegionData(rs);
}
});
} catch (final DataAccessException dae) {
throw new DaoException("Could not retrieve regionList from database. " + dae);
}
}
protected final List<Region> mapRegionData(ResultSet rs) throws SQLException {
final List<Region> regionList = new ArrayList<Region>();
while (rs.next()) {
regionList.add(new Region(rs.getInt("REGION_CD"), rs.getString("REGION_TXT")));
}
return Collections.unmodifiableList(regionList);
}
}
Then I run my test(I took out unneeded crap):
#..annotated with things you don't need to know
public class RetrieveRegionsTest {
#Autowired
private Regions r;
#Test
public void getAndLogRegion() {
final List<Region> regionDescriptions = new ArrayList<Region>(this.r.getRegions());
for (final Region region : regionDescriptions) {
LOGGER.info(region.getValue());
}
}
Yes my configuration and classpaths are set up properly. I can get this to work other ways, just not by accessing the Regions singleton which is what I want. Now I know I could take off the #Autowired on the RegionService in my Regions singleton and just create a new instance of RegionService, but that would defeat the purpose of springs #Autowired feature.
Any thoughts, ideas, comments?

Resources