I have an application where a FakeMailService will be injected if i am not on the Production environment. The configurarion is the following:
#Bean
#Profile("prod")
public MailService mailService(JavaMailSender javaMailSender) {
return new MailServiceImpl(javaMailSender);
}
#Bean
#ConditionalOnMissingBean(MailService.class)
public MailService fakeMailService() {
return new FakeMailService();
}
Now I need to inject que real MailServiceImpl on some specific situations, not caring about the environment. I supposed that it could be achieved using qualified beans, like so:
#Bean
#Profile("prod")
public MailService mailService(JavaMailSender javaMailSender) {
return new MailServiceImpl(javaMailSender);
}
#Bean
#Qualifier("realMailService")
#ConditionalOnMissingBean(MailService.class)
public MailService forceRealMail(JavaMailSender javaMailSender) {
return new MailServiceImpl(javaMailSender);
}
#Bean
#Primary
#ConditionalOnMissingBean(MailService.class)
public MailService fakeMailService() {
return new FakeMailService();
}
And then:
class SomeService {
#Autorwired #Qualifier("realMailService")
MailService theRealMailService;
}
class OtherService {
#Autorwired
MailService fakeMailService;
}
But it seems that doing that the real MailService (MailserviceImpl) will be injected all the time, it does not matter if I am using the qualifier or not.
Am I missing something? Is there eny ther way to achieve that?
Solved it with:
#Bean
#Profile("prod")
public MailService mailService(JavaMailSender javaMailSender) {
return new MailServiceImpl(javaMailSender);
}
#Bean
#Qualifier("realMailService")
#Profile("!prod")
public MailService realMailService(JavaMailSender javaMailSender) {
return new MailServiceImpl(javaMailSender);
}
#Bean
#Primary
#Profile("!prod")
public MailService fakeMailService() {
return new FakeMailService();
}
Related
I've just created a FactoryBean implementation in order to request RestTemplate:
#Component
public class RestTemplateFactory
implements FactoryBean<RestTemplate>, InitializingBean {
//init resttemplate headers
}
So, now I'm able to inject a RestTemplate at whichever class:
#Service
public class DocumentServiceBackOffice {
private RestTemplate restTemplate;
public DocumentServiceBackOffice(RestTemplate restTemplate) {//...}
}
However, I'd like to create another FactoryBean<RestTemplate> in order to initialize other parameters.
How could I create in order to inject one or other according to a qualifier?
Any ideas?
EDIT
#Component
public class RestTemplateFactory
implements FactoryBean<RestTemplate>, InitializingBean {
private RestTemplate restTemplate;
private JWTService jwtService;
public RestTemplateFactory(JWTService jwtService) {
this.jwtService = jwtService;
}
public RestTemplate getObject() {
return this.restTemplate;
}
public Class<RestTemplate> getObjectType() {
return RestTemplate.class;
}
public boolean isSingleton() {
return true;
}
public void afterPropertiesSet() {
this.restTemplate = new RestTemplate();
JWTHeaderRequestInterceptor jwtInterceptor = new JWTHeaderRequestInterceptor(this.jwtService);
this.restTemplate.setInterceptors(Arrays.asList(jwtInterceptor));
}
}
Instead of using a FactoryBean just use an #Bean annotated method which accepts a RestTemplateBuilder and use that to configure instances.
#Bean
#Primary
public RestTemplate fooRestTemplate(RestTemplateBuilder builder, JWTService jwtService) {
return builder.additionalInterceptors(Collections.singletonList(new JwtHeaderInterceptor(jwtService)).build();
}
#Bean
public RestTemplate barRestTemplate(RestTemplateBuilder builder {
return builder.build();
}
This will result in 2 available RestTemplate instances. The fooRestTemplate (marked as default due to #Primary) and barRestTemplate. To specify the specific one to use add an #Qualifier("barRestTemplate") to use the not default one.
public DocumentServiceBackOffice(#Qualifier("barRestTemplate") RestTemplate restTemplate) { ... }
Another way to do it would be defining a configuration with two RestTemplate beans with qualifiers.
#Configuration
public class Configuration {
#Bean
#Qualifier("firstRestTemplate")
public RestTemplate firstRestTemplate(){
// any building logic here
return new Resttemplate();
}
#Bean
#Qualifier("secondRestTemplate")
public RestTemplate secondRestTemplate(){
// any building logic here
return new Resttemplate();
}
}
Then, in your code, use the right #Qualifier when autowiring.
Setter injection example:
#Service
public class Service {
#Autowired
#Qualifier("firstRestTemplate")
private RestTemplate template;
// ...
}
Constructor injection example:
#Service
public class Service {
private RestTemplate template;
public Service(#Autowired #Qualifier("firstRestTemplate") RestTemplate template) {
this.template = template;
}
// ...
}
Any bean inyected into CustomValidator Implementation are always null. Im using Spring Boot 2, the applciation is a REST API, i'm not using MVC.
I have tried everything I have read about this with no luck so far.
this topic here for example did not work for me
Should I validate otherwise?, I've been stuck with this for 2 days already.
This is my Config class:
#Configuration
public class Beans {
#Bean
public BCryptPasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
#Bean
public MessageSource messageSource() {
ReloadableResourceBundleMessageSource messageSource = new ReloadableResourceBundleMessageSource();
messageSource.setBasename("classpath:idiomas/idioma");
messageSource.setUseCodeAsDefaultMessage(true);
messageSource.setDefaultEncoding("UTF-8");
return messageSource;
}
#Bean
public LocaleResolver localeResolver() {
AcceptHeaderLocaleResolver resolver = new AcceptHeaderLocaleResolver();
resolver.setDefaultLocale(new Locale("es"));
return resolver;
}
#Bean
public LocalValidatorFactoryBean getValidator() {
LocalValidatorFactoryBean validatorFactoryBean = new LocalValidatorFactoryBean();
validatorFactoryBean.setValidationMessageSource(messageSource());
return validatorFactoryBean;
}
#Bean
public CommonsRequestLoggingFilter logFilter() {
CommonsRequestLoggingFilter filter = new CommonsRequestLoggingFilter();
filter.setIncludeQueryString(true);
filter.setIncludePayload(true);
filter.setMaxPayloadLength(10000);
filter.setIncludeHeaders(true);
filter.setAfterMessagePrefix("REQUEST: ");
return filter;
}
}
My CustomValidator looks like this:
public class AlmacenValidator implements ConstraintValidator {
#Autowired
private AlmacenService servicio;
#Autowired
private ApplicationContext contexto;
#Override
public void initialize(AlmacenValido constraintAnnotation) {
}
#Override
public boolean isValid(Almacen item, ConstraintValidatorContext context) {
//Database calls
}
JPA Entity:
#Entity
#AlmacenValido
#Table(name = "almacenes")
public class Almacen {
//Entity fields
}
The Annotation:
#Documented
#Retention(RUNTIME)
#Target({ TYPE, FIELD, ANNOTATION_TYPE, PARAMETER })
#Constraint(validatedBy = AlmacenValidator.class)
public #interface AlmacenValido {
String message() default "{validacion.almacen}";
Class[] groups() default {};
Class[] payload() default {};
}
I've created test example for you. Please check it https://github.com/alex-petrov81/stackoverflow-answers/tree/master/validation-bean-injection
I would like to set more than one LDAP repositories with Spring LDAP. My aim is to create or update objects in all repositories at the same time.
I use LdapRepository Spring interface and I think that isn't possible for now.
I wonder if I can create my own LdapRepository extending the Spring one but I have no idea how to start.
This my configuration :
#Configuration
#EnableLdapRepositories("com.xxx.repository.ldap")
#PropertySource("classpath:ldap.properties")
public class LdapConfiguration {
#Autowired
Environment ldapProperties;
#Bean
public LdapContextSourceCustom contextSourceTarget() {
LdapContextSourceCustom ldapContextSource = new LdapContextSourceCustom();
ldapContextSource.setUrl(ldapProperties.getProperty("ldap.url"));
ldapContextSource.setBase(ldapProperties.getProperty("ldap.base"));
ldapContextSource.setUserDn(ldapProperties.getProperty("ldap.userDn"));
ldapContextSource.setPassword(ldapProperties.getProperty("ldap.password"));
ldapContextSource.setKeyStoreFile(ldapProperties.getProperty("ldap.truststore"));
return ldapContextSource;
}
#Bean
public LdapTemplate ldapTemplate(){
return new LdapTemplate(contextSourceTarget());
}
}
And to be complete, one repository:
public interface LdapUserRepository extends LdapRepository<LdapUser> {
}
Any idea how to do it ?
Thanks in advance for any help.
1) It is possible specify more than one LDAP Repository configuration. Please see the following example. [Notice: This depends on spring-boot libraries]
#Configuration
#EnableLdapRepositories("com.xxx.repository.ldap")
#EnableConfigurationProperties(LdapProperties.class)
public class LdapConfiguration {
#Autowired
private Environment environment;
#Bean(name="contextSource1")
public LdapContextSource contextSourceTarget(LdapProperties ldapProperties) {
LdapContextSource source = new LdapContextSource();
source.setUserDn(this.properties.getUsername());
source.setPassword(this.properties.getPassword());
source.setBase(this.properties.getBase());
source.setUrls(this.properties.determineUrls(this.environment));
source.setBaseEnvironmentProperties(Collections.<String,Object>unmodifiableMap(this.properties.getBaseEnvironment()));
return source;
}
#Bean
public LdapTemplate ldapTemplate(#Qualifier("contextSource1") LdapContextSource contextSource){
return new LdapTemplate(contextSource);
}
}
You can use the spring.ldap prefix in application.properties to configure the above LdapConfiguration. You can see the available properties by checking out https://github.com/spring-projects/spring-boot/blob/master/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/ldap/LdapProperties.java.
#Configuration
#EnableLdapRepositories(basePackages="com.yyy.repository.ldap", ldapTemplateRef="ldapTemplate2")
public class LdapConfiguration2 {
#Autowired
private Environment environment;
#Bean(name="ldapProperties2")
#ConfigurationProperties(prefix="spring.ldap2")
public LdapProperties ldapProperties() {
return new LdapProperties();
}
#Bean(name="contextSource2")
public LdapContextSource contextSourceTarget(#Qualifier("ldapProperties2") LdapProperties ldapProperties) {
LdapContextSource source = new LdapContextSource();
source.setUserDn(this.properties.getUsername());
source.setPassword(this.properties.getPassword());
source.setBase(this.properties.getBase());
source.setUrls(this.properties.determineUrls(this.environment));
source.setBaseEnvironmentProperties(Collections.<String,Object>unmodifiableMap(this.properties.getBaseEnvironment()));
return source;
}
#Bean(name="ldapTemplate2")
public LdapTemplate ldapTemplate(#Qualifier("contextSource2") LdapContextSource contextSource){
return new LdapTemplate(contextSource);
}
}
LdapConfiguration2 will be configured by the spring.ldap2 prefix in application.properties.
2) I don't think extending the Repository is the solution. I would recommend creating a #Service method that iterated through your repositories and applied the updates. I will provide two approaches below.
Example 1)
#Service
public class UpdateRepositories {
public void updateAllRepositories(LdapUserRepository userRepository1, LdapUserRepository userRepository2) {
// apply updates to userRepository1 and userRepository2
}
}
Example 2)
#Service
public class UpdateRepositories {
public void updateAllRepositories(ApplicationContext appContext) {
Map<String, LdapRepository> ldapRepositories = appContext.getBeansofType(LdapRepository.class)
// iterate through map and apply updates
}
}
I haven't compiled this code, so let me know if something is off or if you need additional guidance.
I don't known if I understood correctly but here is what we did:
Global configuration class
#Bean("odm")
public ObjectDirectoryMapper odm() {
return new DefaultObjectDirectoryMapper();
};
First LDAP configuration class
#Configuration
#PropertySource("classpath:ldap-one.properties")
public class LdapOneConfiguration {
#Autowired
Environment ldapProperties;
#Bean(name = "contextSourceOne")
public LdapContextSourceCustom contextSourceLdapOneTarget() {
LdapContextSourceCustom ldapContextSource = new LdapContextSourceCustom();
ldapContextSource.setUrl(ldapProperties.getProperty("ldap-one.url"));
ldapContextSource.setBase(ldapProperties.getProperty("ldap-one.base"));
ldapContextSource.setUserDn(ldapProperties.getProperty("ldap-one.userDn"));
ldapContextSource.setPassword(ldapProperties.getProperty("ldap-one.password"));
ldapContextSource.setKeyStoreFile(ldapProperties.getProperty("ldap-one.truststore"));
return ldapContextSource;
}
#Bean(name = "ldapTemplateOne")
public LdapTemplate ldapOneTemplate(#Qualifier("contextSourceOne") LdapContextSourceCustom contextSource) {
return new LdapTemplate(contextSource);
}
#Bean(name = "ldapUserRepoOne")
public LdapUserRepository ldapUserRepositoryOne(#Qualifier("ldapTemplateOne") LdapTemplate ldapTemplate,
#Qualifier("odm") ObjectDirectoryMapper odm) {
return new LdapUserRepository(ldapTemplate, odm);
}
#Bean(name = "ldapFamilyRepoOne")
public LdapFamilyRepository ldapFamilyRepositoryOne(#Qualifier("ldapTemplateOne") LdapTemplate ldapTemplate,
#Qualifier("odm") ObjectDirectoryMapper odm) {
return new LdapFamilyRepository(ldapTemplate, odm);
}
}
Second LDAP configuration class
#Configuration
#PropertySource("classpath:ldap-two.properties")
public class LdapTwoConfiguration {
#Autowired
Environment ldapProperties;
#Bean(name = "contextSourceTwo")
public LdapContextSourceCustom contextSourceLdapTwoTarget() {
LdapContextSourceCustom ldapContextSource = new LdapContextSourceCustom();
ldapContextSource.setUrl(ldapProperties.getProperty("ldap-two.url"));
ldapContextSource.setBase(ldapProperties.getProperty("ldap-two.base"));
ldapContextSource.setUserDn(ldapProperties.getProperty("ldap-two.userDn"));
ldapContextSource.setPassword(ldapProperties.getProperty("ldap-two.password"));
ldapContextSource.setKeyStoreFile(ldapProperties.getProperty("ldap-two.truststore"));
return ldapContextSource;
}
#Bean(name = "ldapTemplateTwo")
public LdapTemplate ldapTwoTemplate(#Qualifier("contextSourceTwo") LdapContextSourceCustom contextSource) {
return new LdapTemplate(contextSource);
}
#Bean(name = "ldapUserRepoTwo")
public LdapUserRepository ldapUserRepositoryTwo(#Qualifier("ldapTemplateTwo") LdapTemplate ldapTemplate,
#Qualifier("odm") ObjectDirectoryMapper odm) {
return new LdapUserRepository(ldapTemplate, odm);
}
#Bean(name = "ldapFamilyRepoTwo")
public LdapFamilyRepository ldapFamilyRepositoryTwo(#Qualifier("ldapTemplateTwo") LdapTemplate ldapTemplate,
#Qualifier("odm") ObjectDirectoryMapper odm) {
return new LdapFamilyRepository(ldapTemplate, odm);
}
}
LdapUser repository
public class LdapUserRepository extends SimpleLdapRepository<LdapUser> {
public LdapUserRepository(LdapOperations ldapOperations, ObjectDirectoryMapper odm) {
super(ldapOperations, odm, LdapUser.class);
}
}
LdapFamily repository
public class LdapFamilyRepository extends SimpleLdapRepository<LdapFamily> {
public LdapFamilyRepository(LdapOperations ldapOperations, ObjectDirectoryMapper odm) {
super(ldapOperations, odm, LdapFamily.class);
}
}
LdapUser service (same for LdapFamily service)
#Service
public class LdapUserServiceImpl implements LdapUserService {
#Autowired
private ApplicationContext appContext;
private LdapUserRepository uniqueLdapUserRepo;
private List<LdapUserRepository> ldapUserRepoList;
#PostConstruct
private void setUniqueRepo() {
uniqueLdapUserRepo = appContext.getBeansOfType(LdapUserRepository.class).values().iterator().next();
ldapUserRepoList = new ArrayList<>(appContext.getBeansOfType(LdapUserRepository.class).values());
}
#Override
public LdapUser getUser(String uid) {
return uniqueLdapUserRepo.findOne(query().where("uid").is(uid));
}
#Override
public void saveUser(LdapUser user) {
for(LdapUserRepository repo: ldapUserRepoList){
repo.save(user);
}
}
}
We deleted the auto configuration of LDAP repo:
#EnableLdapRepositories(basePackages = "com.afklm.paul.repository.ldap", ldapTemplateRef = "ldapTwoTemplate")
Thanks ryan2049 for your help.
there is actually an easier way now:
create multiple configuration that is anotated with #EnableLdapRepositories with corresponding attributes
Create first configuration
#Configuration
#EnableLdapRepositories(basePackages = "first.ldap.package.repository.**", ldapTemplateRef = "firstLdapTemplate")
public class FirstLDAPConfig {
....detail
#Bean("firstLdapTemplate")
public LdapTemplate firstLdapTemplate() {
...template creation
}
}
Create second configuration
#Configuration
#EnableLdapRepositories(basePackages = "second.ldap.package.repository.**", ldapTemplateRef = "secondLdapTemplate")
public class SecondLDAPConfig {
....detail
#Bean("secondLdapTemplate")
public LdapTemplate secondLdapTemplate() {
...template creation
}
}
each configuration should handle it's own contextSource
then only the specified repository within the EnableLdapRepositories annotation will use that specific ContextSource and LdapTemplate
I'm still new to the axon frame work.
I'm trying to implement snapshotting using mongodb in my application and I keep on getting an error saying
"AbstractSnapshotter : An attempt to create and store a snapshot resulted in an exception. Exception summary: Aggregate Type is unknown in this snapshotter: com.myworklife.contacts.domain.contact.Contact"
This is a part of my java config file.
#Bean
public AggregateSnapshotter snapShotter(EventStore eventStore, AggregateFactory<Contact> contactAggregateFactory) {
return new AggregateSnapshotter(eventStore);
}
#Bean
public SnapshotTriggerDefinition snapshotTriggerDefinition(Snapshotter snapShotter) throws Exception {
return new EventCountSnapshotTriggerDefinition(snapShotter, 1);
}
#Bean
public EventStore eventStore(EventStorageEngine eventStorageEngine) {
return new EmbeddedEventStore(eventStorageEngine);
}
#Bean
public Repository<Contact> contactAggregateRepository(EventStore eventStore, SnapshotTriggerDefinition snapshotTriggerDefinition) {
return new ContactRepository(eventStore, snapshotTriggerDefinition);
}
And my repository.
#Repository("ContactRepository")
public class ContactRepository extends EventSourcingRepository<Contact> {
#Autowired
public ContactRepository(EventStore eventStore, SnapshotTriggerDefinition snapshotTriggerDefinition) {
super(Contact.class, eventStore, snapshotTriggerDefinition);
}
public Contact findContact(ContactId contactId) {
return load(contactId.toString()).getWrappedAggregate().getAggregateRoot();
}
}
My aggregate.
#Aggregate(repository="contactAggregateRepository")
public class Contact {
#AggregateIdentifier
private ContactId id;
private String name;
private String mobileNumber;
public Contact() {
// do nothing, Axon requires default constructor
}
#CommandHandler
public Contact(CreateContactCommand createContactCommand) {
apply(new ContactHasBeenCreatedEvent(createContactCommand.getContactId(), createContactCommand.getName(),
createContactCommand.getMobileNumber()));
}
}
Is there something I'm doing wrong?
since I'm getting an error saying "An attempt to create and store a snapshot resulted in an exception. Exception summary: Aggregate Type is unknown in this snapshotter: com.myworklife.contacts.domain.contact.Contact"
Any help will be highly appreciated.
Thanks,
Pat
You need to add the contactAggregateFactory to the constructor of the AggregateSnapshotter in the snapShotter bean:
#Bean
public AggregateSnapshotter snapShotter(EventStore eventStore, AggregateFactory<Contact> contactAggregateFactory) {
return new AggregateSnapshotter(eventStore, contactAggregateFactory);
}
1.depencity jar
<dependency>
<groupId>org.axonframework</groupId>
<artifactId>axon-spring-boot-autoconfigure</artifactId>
<version>${axon.version}</version>
</dependency>
2.At first,you should config your application.ymp or bootstrap.yml,like this:spring:
data:
mongodb:
host: localhost
port: 27017
database: axonframework
events: domainevents
snapshots: snapshotevents
3.config your mongoDB:
#Bean(name = "axonMongoTemplate")
public MongoTemplate axonMongoTemplate() {
MongoTemplate template = new DefaultMongoTemplate(mongoClient(), mongoDbName, eventsCollectionName, snapshotCollectionName);
return template;
}
#Bean
public MongoClient mongoClient(){
MongoFactory mongoFactory = new MongoFactory();
mongoFactory.setMongoAddresses(Arrays.asList(new ServerAddress(mongoHost)));
return mongoFactory.createMongo();
}
#Bean
public EventStorageEngine eventStorageEngine(Serializer serializer){
return new MongoEventStorageEngine(
serializer,null, axonMongoTemplate(), new DocumentPerEventStorageStrategy());
4.config repository for your aggregate,for example,i config a Element Aggreaget's repository
#Configuration
public class ElementConfiguration {
#Autowired
private EventStore eventStore;
#Bean
#Scope("prototype")
public Element elementAggregate() {
return new Element();
}
#Bean
public AggregateFactory<Element> elementAggregateAggregateFactory() {
SpringPrototypeAggregateFactory<Element> aggregateFactory = new SpringPrototypeAggregateFactory<>();
aggregateFactory.setPrototypeBeanName("elementAggregate");
return aggregateFactory;
}
#Bean
public SpringAggregateSnapshotterFactoryBean springAggregateSnapshotterFactoryBean(){
SpringAggregateSnapshotterFactoryBean factory = new SpringAggregateSnapshotterFactoryBean();
return factory;
}
#Bean
public Repository<Element> elementAggregateRepository(Snapshotter snapshotter) {
EventCountSnapshotTriggerDefinition eventCountSnapshotTriggerDefinition = new EventCountSnapshotTriggerDefinition(snapshotter, 3);
EventSourcingRepository<Element> repository = new EventSourcingRepository<Element>(
elementAggregateAggregateFactory(),
eventStore,
eventCountSnapshotTriggerDefinition
);
return repository;
}
5.enjoy
If you're using Axon 3.3 and SpringBoot 2, this is how we did it in this project:
#Configuration
public class SnapshotConfiguration {
#Bean
public SpringAggregateSnapshotter snapshotter(ParameterResolverFactory parameterResolverFactory,
EventStore eventStore,
TransactionManager transactionManager) {
// https://docs.axoniq.io/reference-guide/v/3.3/part-iii-infrastructure-components/repository-and-event-store#snapshotting
// (...) By default, snapshots are created in the thread that calls the scheduleSnapshot() method, which is generally not recommended for production (...)
Executor executor = Executors.newSingleThreadExecutor();
return new SpringAggregateSnapshotter(eventStore, parameterResolverFactory, executor, transactionManager);
}
#Bean
public EventCountSnapshotTriggerDefinition snapshotTrigger(SpringAggregateSnapshotter snapshotter) {
int snapshotThreshold = 42;
return new EventCountSnapshotTriggerDefinition(snapshotter, snapshotThreshold);
}
}
And if you need the EventStore configuration:
#Configuration
public class AxonMongoEventStoreConfiguration {
#Bean
public MongoClient axonMongoClient(MongoClientURI axonMongoClientUri) {
return new MongoClient(axonMongoClientUri);
}
#Bean
public MongoClientURI axonMongoClientUri() {
return new MongoClientURI("mongodb://host:port/database");
}
#Bean
#Primary
public EventStorageEngine eventStore(MongoClient axonMongoClient, MongoClientURI axonMongoClientUri) {
DefaultMongoTemplate mongoTemplate = new DefaultMongoTemplate(axonMongoClient, axonMongoClientUri.getDatabase());
return new MongoEventStorageEngine(mongoTemplate);
}
}
I have a JavaConfig configurated Spring Batch job. The main job configuration file is CrawlerJobConfiguration. Before now, I have all the configuration (infrastructure, autowired beans, etc) in this class and it works fine. So I decided to separate the job configuration from autowired beans and infracstruture beans configuration and create another 2 configuration classes Beans and MysqlInfrastructureConfiguration.
But now I am having problems to run my job. I'm receiving a NullPointerException when the application try to use autowired fields indicating that the autowired is not working.
I put a breakpoint in the methods that create autowired beans to make sure that they are being called and really are, so I cannot realize what can be the problem.
java.lang.NullPointerException: null
at br.com.alexpfx.supermarket.batch.tasklet.StartCrawlerTasklet.execute(StartCrawlerTasklet.java:27) ~[classes/:na]
at org.springframework.batch.core.step.tasklet.TaskletStep$ChunkTransactionCallback.doInTransaction(TaskletStep.java:406) ~[spring-batch-core-3.0.6.RELEASE.jar:3.0.6.RELEASE]
Main job configuration class:
#Configuration
#EnableBatchProcessing
public class CrawlerJobConfiguration {
#Autowired
private InfrastructureConfiguration infrastructureConfiguration;
#Autowired
private StepBuilderFactory steps;
#Autowired
Environment environment;
#Bean
public Job job(JobBuilderFactory jobs) {
Job theJob = jobs.get("job").start(crawlerStep()).next(processProductStep()).build();
((AbstractJob) theJob).setRestartable(true);
return theJob;
}
#Bean
public Step crawlerStep() {
TaskletStep crawlerStep = steps.get("crawlerStep").tasklet(crawlerTasklet()).build();
crawlerStep.setAllowStartIfComplete(true);
return crawlerStep;
}
#Bean
public Step processProductStep() {
TaskletStep processProductStep = steps.get("processProductStep")
.<TransferObject, Product>chunk(10)
.reader(reader())
.processor(processor())
.writer(writer())
.build();
processProductStep.setAllowStartIfComplete(true);
return processProductStep;
}
private Tasklet crawlerTasklet() {
return new StartCrawlerTasklet();
}
private ItemProcessor<TransferObject, Product> processor() {
return new ProductProcessor();
}
private ItemReader<TransferObject> reader() {
return new ProductItemReader();
}
private ItemWriter<Product> writer() {
return new HibernateProductsItemWriter();
}
}
Beans configuration class:
#Configuration
#EnableBatchProcessing
public class Beans {
#Bean
public Crawler crawler() {
return new RibeiraoCrawler(new UserAgentFactory());
}
#Bean
public ProductBo productBo() {
return new ProductBoImpl();
}
#Bean
public ProductDao productDao() {
return new ProductDaoImpl();
}
#Bean
public CrawlerListener listener() {
CrawlerListener listener = new RibeiraoListener();
return listener;
}
#Bean
public ProductList getProductList() {
return new ProductList();
}
}
MysqlInfrastructureConfiguration:
#Configuration
#EnableBatchProcessing
#PropertySource("classpath:database.properties")
#EnableJpaRepositories(basePackages = {"br.com.alexpfx.supermarket.domain"})
public class MysqlInfrastructureConfiguration implements InfrastructureConfiguration {
#Value("${jdbc.url}")
String url;
#Value("${jdbc.driverClassName}")
String driverClassName;
#Value("${jdbc.username}")
String username;
#Value("${jdbc.password}")
String password;
#Bean
#Override
public DataSource getDataSource() {
DriverManagerDataSource dataSource = new DriverManagerDataSource();
dataSource.setDriverClassName(driverClassName);
dataSource.setUrl(url);
dataSource.setUsername(username);
dataSource.setPassword(password);
return dataSource;
}
#Bean
#Override
public PlatformTransactionManager transactionManager() {
JpaTransactionManager transactionManager = new JpaTransactionManager();
transactionManager.setEntityManagerFactory(entityManagerFactory());
transactionManager.setDataSource(getDataSource());
return transactionManager;
}
#Bean
#Override
public EntityManagerFactory entityManagerFactory() {
LocalContainerEntityManagerFactoryBean em = new LocalContainerEntityManagerFactoryBean();
em.setDataSource(getDataSource());
em.setPackagesToScan(new String[]{"br.com.alexpfx.supermarket.domain"});
JpaVendorAdapter vendorAdapter = new HibernateJpaVendorAdapter();
em.setJpaVendorAdapter(vendorAdapter);
em.setJpaProperties(additionalJpaProperties());
em.afterPropertiesSet();
return em.getObject();
}
private Properties additionalJpaProperties() {
Properties properties = new Properties();
properties.setProperty("hibernate.hbm2ddl.auto", "create");
properties.setProperty("hibernate.dialect", "org.hibernate.dialect.MySQL5Dialect");
properties.setProperty("hibernate.show_sql", "true");
properties.setProperty("current_session_context_class", "thread");
return properties;
}
}
tasklet:
public class StartCrawlerTasklet implements Tasklet {
#Autowired
private Crawler crawler;
#Autowired
private CrawlerListener listener;
#Override
public RepeatStatus execute(StepContribution stepContribution, ChunkContext chunkContext) throws Exception {
crawler.setListener(listener);
crawler.setStopCondition(new TimeLimitStopCondition(1, TimeUnit.MINUTES));
crawler.crawl();
return RepeatStatus.FINISHED;
}
}
StartCrawlerTasklet use the autowired annotation, so it should be a bean as well. So change your code :
private Tasklet crawlerTasklet() {
return new StartCrawlerTasklet();
}
to a bean definition:
#Bean
public Tasklet crawlerTasklet() {
return new StartCrawlerTasklet();
}