MongoCK ChangeUnit doesn't auto wire other spring beans as dependency
anadimisra opened this issue · comments
Description
How do I make the ChangeUnit work with other spring beans in my app, ChangeUnits doesn't seem to be importing the dependencies before running the execution annotated method.
PRIORITY
[CRITICAL]
Version and environment
Mongock
- Mongock version - 5.3.4
- Modules involved(springboot, reactive-driver)
- How Mongock is used(builder or annotation approach, etc.) builder approach.
Environment
- Framework and libraries versions. Especially those that affect directly to Mongock(Spring, Spring data, MongoDB driver, etc.) - Spring Boot version 2.7.1, Mongo 4.4, MongoDB-driver-reactivestreams 4.6.1, spring-data-mongodb 3.4.14
Steps to Reproduce
I've tried adding dependencies to the ChangeUnit via construction injection and setter injection, and using post construct to build the data I want to seed. But I get a
Caused by: java.lang.IllegalArgumentException: documents can not be null
exception while running the change unit, at line
mongoDatabase.getCollection("form_layouts", FormLayout.class)
.insertMany(clientSession, formLayouts)
.subscribe(insertSubscriber);
here's the code
private List<FormLayout> formLayouts;
@Autowired
private Jackson2ObjectMapperBuilder objectMapperBuilder;
@Autowired
private AppProperties appProperties;
@Value("classpath:form_layouts.json")
private Resource layouts;
@PostConstruct
public void initializeLayoutsJson() throws IOException {
ObjectMapper objectMapper = objectMapperBuilder.failOnUnknownProperties(Boolean.TRUE).build();
this.formLayouts = objectMapper.readValue(Files.readString(Path.of(layouts.getFile().getAbsolutePath())), new TypeReference<>() {
});
for (FormLayout layout : formLayouts) {
layout.setRealmId(appProperties.getRealmId());
}
log.debug("Loaded {} Layouts from file seeder JSON {}", formLayouts.size(), layouts.getFilename());
int i = 0;
for (FormLayout layout : formLayouts) {
i++;
log.debug("Layout-{} : {}", i, layout);
}
}
@Execution
public void migrationMethod(ClientSession clientSession, MongoDatabase mongoDatabase) {
final SubscriberSync<InsertManyResult> insertSubscriber = new MongoSubscriberSync<>();
mongoDatabase.getCollection("form_layouts", FormLayout.class)
.insertMany(clientSession, this.formLayouts)
.subscribe(insertSubscriber);
InsertManyResult result = insertSubscriber.getFirst();
log.info("{}.execution() wasAcknowledged: {}", this.getClass().getSimpleName(), result.wasAcknowledged());
result.getInsertedIds()
.forEach((key, value) -> log.info("Added Object[{}] : {}", key, value));
}
I've also tried constructor injection
private final List<FormLayout> formLayouts;
public LayoutsDataInitializer(@Autowired Jackson2ObjectMapperBuilder objectMapperBuilder, @Autowired AppProperties appProperties,
@Value("classpath:form_layouts.json") Resource layouts) throws IOException {
ObjectMapper objectMapper = objectMapperBuilder.failOnUnknownProperties(Boolean.TRUE).build();
this.formLayouts = objectMapper.readValue(Files.readString(Path.of(layouts.getFile().getAbsolutePath())), new TypeReference<>() {
});
for (FormLayout layout : formLayouts) {
layout.setRealmId(appProperties.getRealmId());
}
}
@Execution
public void migrationMethod(ClientSession clientSession, MongoDatabase mongoDatabase) throws IOException {
final SubscriberSync<InsertManyResult> insertSubscriber = new MongoSubscriberSync<>();
mongoDatabase.getCollection("form_layouts", FormLayout.class)
.insertMany(clientSession, formLayouts)
.subscribe(insertSubscriber);
InsertManyResult result = insertSubscriber.getFirst();
log.info("{}.execution() wasAcknowledged: {}", this.getClass().getSimpleName(), result.wasAcknowledged());
result.getInsertedIds()
.forEach((key, value) -> log.info("Added Object[{}] : {}", key, value));
}
This gives a rather cryptic error about wrong Dependency Caused by: io.mongock.driver.api.common.DependencyInjectionException: Wrong parameter[Resource]. Dependency not found.
in ChangeLogRuntimeImpl. Moving the entire logic into execution method also does not help.
@Value("classpath:form_layouts.json")
Resource layouts;
@Execution
public void migrationMethod(ClientSession clientSession, MongoDatabase mongoDatabase) throws IOException {
ObjectMapper objectMapper = new ObjectMapper();
objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, Boolean.TRUE);
List<FormLayout> formLayouts = objectMapper.readValue(Files.readString(Path.of(layouts.getFile().getAbsolutePath())), new TypeReference<>() {
});
for (FormLayout layout : formLayouts) {
layout.setRealmId(REALM_ID);
}
final SubscriberSync<InsertManyResult> insertSubscriber = new MongoSubscriberSync<>();
mongoDatabase.getCollection("form_layouts", FormLayout.class)
.insertMany(clientSession, formLayouts)
.subscribe(insertSubscriber);
InsertManyResult result = insertSubscriber.getFirst();
log.info("{}.execution() wasAcknowledged: {}", this.getClass().getSimpleName(), result.wasAcknowledged());
result.getInsertedIds()
.forEach((key, value) -> log.info("Added Object[{}] : {}", key, value));
}
Fails as the layouts object is reported Null.
Behaviour
Expected behavior:
ChangeUnit to be applied successfully.
Actual behavior:
Application startup fails due to errors mentioned in the steps to reproduce section.
How often the bug happens:
100%
Hello @anadimisra , your question is answered in stackoverflow. Please take a look and it's useful, mark the answer as useful, that will help us a lot.
Thanks
Thanks @dieppa ! I'll detail my findings here
- It works if the fields autowired in the constructor are also private final in the bean and I set them. Now there can be a case where I'm autowiring constructor dependencies for initialising other private fields of this object and I don't need the autowired beans outside of the constructor - in such cases I get the wrong Parameter error which I should not
- While other setter injection based and method parameter based dependency injection works,
@Value
annotated parameters don't get autowired definitely. Like in my case it was@Value("classpath:form_templates") Resource templateFile
I have answered via stackoverflow. I explained the current limitations, offer a potential workaround and suggest to contribute 😄
It will help a lot if you can mark the answer as helpful.
Thanks