Wire generic types in spring-boot configuration - java

I have a following bean's definition:
#Configuration
class PanelApiConfiguration {
#Bean
PanelApi panelApi(ServiceFactory.Builder<EmptyContext> serviceFactoryBuilder) {
ServiceFactory<EmptyContext> serviceFactory = serviceFactoryBuilder.build()
return serviceFactory.create(PanelApi.class);
}
#Bean
ServiceFactoryCustomizer<EmptyContext> emptyContextServiceFactoryCustomizer() {
return () -> Duration.ZERO;
}
#Bean
AnotherApi anotherApi(ServiceFactory.Builder<AnotherContext> serviceFactoryBuilder) {
ServiceFactory<AnotherContext> serviceFactory = serviceFactoryBuilder.build()
return serviceFactory.create(AnotherApi.class);
}
#Bean
ServiceFactoryCustomizer<AnotherContext> anotherContextServiceFactoryCustomizer() {
return () -> Duration.ofSeconds(10);
}
}
I would like to wire:
ServiceFactory.Builder<EmptyContext> serviceFactoryBuilder with ServiceFactoryCustomizer<EmptyContext> emptyContextServiceFactoryCustomizer
ServiceFactory.Builder<AnotherContext> serviceFactoryBuilder with ServiceFactoryCustomizer<AnotherContext> anotherContextServiceFactoryCustomizer
and so on. Each builder have his own customizer - based on the generic type.
ServiceFactory.Builder is created as a auto-configuration bean:
#Bean
#Scope(scopeName = SCOPE_PROTOTYPE)
public ServiceFactory.Builder<?> serviceFactoryBuilder(
ObjectMapper objectMapper,
ClientHttpConnector clientHttpConnector
) {
return builder()
.objectMapper(objectMapper)
.clientHttpConnector(clientHttpConnector);
}
Is there any possibility to do that? Honestly, I don't have any idea how to wire this classes.

Related

How to create a #Transaction in Spring to control rollback with two different databases?

I have two different databases oracle.
I have implemented the confuration to work with two databases Using SpringBoot:
#Configuration
#EnableTransactionManagement
#EnableJpaRepositories(basePackages = "br.com.xyz.univers.ms.universcontractsync.repository.universcontract",
entityManagerFactoryRef = "universContractEntityManagerFactory",
transactionManagerRef= "universContractTransactionManager"
)
public class UniversContractDatasourceConfiguration {
#Bean
#ConfigurationProperties("app.datasource.universcontract")
public DataSourceProperties universContractDataSourceProperties() {
return new DataSourceProperties();
}
#Bean
#ConfigurationProperties("app.datasource.universcontract.hikari")
public DataSource universContractDataSource() {
return universContractDataSourceProperties().initializeDataSourceBuilder()
.type(HikariDataSource.class).build();
}
#Bean(name = "universContractEntityManagerFactory")
public LocalContainerEntityManagerFactoryBean universContractEntityManagerFactory(
EntityManagerFactoryBuilder builder) {
return builder
.dataSource(universContractDataSource())
.packages("br.com.xyz.univers.ms.universcontractsync.models.universcontract")
.build();
}
#Bean(name = "universContractTransactionManager")
public PlatformTransactionManager universContractTransactionManager(
final #Qualifier("universContractEntityManagerFactory") LocalContainerEntityManagerFactoryBean universContractEntityManagerFactory) {
if (universContractEntityManagerFactory == null) {
throw new SyncUnknowException("universContractEntityManagerFactory is null");
}
EntityManagerFactory entityManagerFactory = universContractEntityManagerFactory.getObject();
if (entityManagerFactory == null) {
throw new SyncUnknowException("entityManagerFactory is null");
}
return new JpaTransactionManager(entityManagerFactory);
}
}
The problem is how can i control the transaction when a method call the two databases? It's necessary because my method needs to all the both databases.
Example the method:
#Override
#Transactional("chainedTransactionManager")
public void save(CompanyDTO companyDTO) {
log.info("method=save ' - CompanyServiceImpl");
CompanyDTO companyDTOWithLegacyCode = companyDTO.toBuilder().build();
legacyCodeService.changeToLegacyCode(companyDTOWithLegacyCode);
Company company = companyBuilder.universContractDtoToSisbfEntity(companyDTOWithLegacyCode);
companyRepository.save(company);
legacyCodeService.updateLegacyCode(ClassTypeEnum.COMPANY, companyDTO.getCompanyCode(), company.getCompanyCode());
log.info("finish method=save ' - CompanyServiceImpl");
}
Use #Transactional annotation for both databases by specifying its value explicitly. Example #Transactional(value="chainedTransactionManager"). So that spring takes care of that.

Is it possible to create one configuration for some classes with Orika mapper?

I try to config Orika mapper. I have 5 entities and 5 DTO. My configuration work true, but I can't find any information about how to configure mapper for different classes. So is it possible to create one configuration for some classes or I should create configuration for every pair of classes?
#Configuration
public class MapperConfig implements OrikaMapperFactoryConfigurer {
#Bean
DatatypeFactory datatypeFactory() throws DatatypeConfigurationException {
return DatatypeFactory.newInstance();
}
#Bean
DefaultMapperFactory.MapperFactoryBuilder<?, ?> orikaMapperFactoryBuilder() {
DefaultMapperFactory.Builder orikaMapperFactoryBuilder = new DefaultMapperFactory.Builder();
return orikaMapperFactoryBuilder;
}
#Bean
public MapperFactory orikaMapperFactory(DefaultMapperFactory.MapperFactoryBuilder<?, ?> orikaMapperFactoryBuilder) {
MapperFactory orikaMapperFactory = orikaMapperFactoryBuilder.build();
this.configure(orikaMapperFactory);
return orikaMapperFactory;
}
public void configure(MapperFactory orikaMapperFactory) {
orikaMapperFactory.classMap(Author.class, AuthorDto.class)
.byDefault()
.register();
}
#Bean
public MapperFacade orikaMapperFacade(MapperFactory orikaMapperFactory) {
MapperFacade orikaMapperFacade = orikaMapperFactory.getMapperFacade();
return orikaMapperFacade;
}
}
Yes you can, you need to register mapper for other two pairs of classes in configure method
Example:
public void configure(MapperFactory orikaMapperFactory) {
orikaMapperFactory.classMap(Author.class, AuthorDto.class)
.byDefault()
.register();
orikaMapperFactory.classMap(A.class, B.class)
.byDefault()
.register();
}

Spring inject a bean into another bean

I am trying to inject a bean into another bean that uses it. How can I do this?
public class MySpringConfig{
#Bean
public MyObject getMyObject() {
//.....
return MyObjectInstance;
}
#Bean
public SomeObject getSomeObject(MyObject myObject) {
//.....
return SomeObjectInstance;
}
}
I think you can do this with this way, this is working in my project.
#Configuration
public class AppConfig {
#Bean
public Bean1 foo(#Qualifier("bean2") Bean2 bean2) {
return new Bean1(bean2);
}
}
i think that might work!
#Configuration
public class AppConfig {
#Bean
public Bean2 bean2() {
return new Bean2();
}
#Bean
#DependsOn({"bean2"})
public Bean1 foo(#Autowired Bean2 bean2) {
return new Bean1(bean2); // or your can write new Bean1(bean2());
}
}
Parameters don't work exactly in the same way in #Bean and #Component.
For a class annotated with #Component, specifying them is required for the autowired constructor but in a #Bean declaration you don't need to provide a parameter to specify the MyObject dependency to use (while it will work) if that is accessible in the current class, which is your case.
So you want to inject directly the bean by invoking getMyObject() in the #Bean definition.
For example to pass it a constructor arg :
#Bean
public SomeObject getSomeObject() {
//....
// you injected MyObject in the current bean to create
SomeObject object = new SomeObject(getMyObject());
//...
return SomeObjectInstance;
}
And don't forget to annotate the class with #Configuration to make it considered by Spring.

How to use multiple spring configuration files

I have a java spring configuration defined like so,
#Configuration
public class FirstConfiguration {
#Bean
FirstController firstController() {
return new FirstController(firstService());
}
#Bean
FirstService firstService() {
return new FirstServiceImpl(secondService());
}
}
Now the beans in this configuration depend on SecondConfiguration defined like so,
#Configuration
public class SecondConfiguration {
#Bean
SecondController SecondController() {
return new SecondController(SecondService());
}
#Bean
SecondService secondService() {
return new SecondServiceImpl();
}
}
How can I make use of the secondService() bean in my FirstConfiguration?
Since the SecondService is a bean, you could inject it into the firstService method to configure another bean:
#Bean
FirstService firstService(#Autowired SecondService secondService) {
return new FirstServiceImpl(secondService);
}
You can inject the firstService like this :
#Autowired
SecondService secondService
You can refer the method secondService() directly when you import the configuration.
#Configuration
#Import(SecondConfiguration.class)
public class FirstConfiguration {
#Bean
FirstController firstController() {
return new FirstController(firstService());
}
#Bean
SomeController someController() {
return new SomeController(secondService());
}
}
Refer the Spring config import

How to set up a unit test for spring data with service and repository?

I have checked many SO comments and the docs for spring data and unit testing but I cant get this to work and I dont know why its not working.
I have a junit test class that looks like this:
#RunWith(SpringJUnit4ClassRunner.class)
#ContextConfiguration(loader = AnnotationConfigContextLoader.class)
public class DealServiceTest {
#Configuration
static class ContextConfiguration {
// this bean will be injected into the OrderServiceTest class
#Bean
public DealService orderService() {
DealService dealService = new DealService();
// set properties, etc.
return dealService;
}
#Bean
public EmployeeService employeeService(){
EmployeeService employeeService = new EmployeeService();
return employeeService;
}
}
#Autowired
DealService dealService;
#Autowired
EmployeeService employeeService;
#Test
public void createDeal() throws ServiceException {
Employee employee = new Employee("Daniel", "tuttle", "danielptm#me.com", "dannyboy", "secret password", 23.234, 23.23);
Deal d = dealService.createDeal("ADSF/ADSF/cat.jpg", "A title goes here", "A deal description", 23.22, "Name of business", 23.23,23.23, employee, "USA" );
Assert.assertNotNull(d);
}
}
And then I have my service class that looks like this
#Service
public class DealService {
#Autowired
private DealRepository dealRepository;
public Deal createDeal(String image, String title, String description, double distance, String location, double targetLat, double targetLong, Employee employee, String country) throws ServiceException {
Deal deal = new Deal(image, title, description, distance, location, targetLat, targetLong, employee, country);
try {
return dealRepository.save(deal);
}catch(Exception e){
throw new ServiceException("Could not create a deal: "+deal.toString(), e);
}
}
public Deal updateDeal(Deal d) throws ServiceException {
try{
return dealRepository.save(d);
}catch(Exception e){
throw new ServiceException("Could not update deal at this time: "+d.toString(),e);
}
}
public List<Deal> getAllDealsForEmployeeId(Employee employee) throws ServiceException {
try{
return dealRepository.getAllDealsBy_employeeId(employee.getId());
}catch(Exception e){
throw new ServiceException("Could not get deals for employee: "+employee.getId(), e);
}
}
}
And then my repository:
*/
public interface DealRepository extends CrudRepository<Deal, Long>{
public List<Deal> getDealsBy_country(String country);
public List<Deal> getAllDealsBy_employeeId(Long id);
}
My config file looks like this:
#Configuration
#EnableJpaRepositories("com.globati.repository")
#EnableTransactionManagement
public class InfrastructureConfig {
#Bean
public DataSource dataSource() {
HikariConfig config = new HikariConfig();
config.setDriverClassName("com.mysql.jdbc.Driver");
config.setJdbcUrl("jdbc:mysql://localhost:3306/DatabaseProject");
config.setUsername("awesome");
config.setPassword("database");
return new HikariDataSource(config);
}
// #Bean
// public DataSource derbyDataSource(){
// HikariConfig config = new HikariConfig();
// config.setDriverClassName("jdbc:derby:memory:dataSource");
// config.setJdbcUrl("jdbc:derby://localhost:1527/myDB;create=true");
// config.setUsername("awesome");
// config.setPassword("database");
//
// return new HikariDataSource(config);
//
// }
#Bean
public JpaTransactionManager transactionManager(EntityManagerFactory factory) {
return new JpaTransactionManager(factory);
}
#Bean
public JpaVendorAdapter jpaVendorAdapter() {
HibernateJpaVendorAdapter adapter = new HibernateJpaVendorAdapter();
adapter.setDatabase(Database.MYSQL);
adapter.setGenerateDdl(true);
return adapter;
}
#Bean
public LocalContainerEntityManagerFactoryBean entityManagerFactory() {
LocalContainerEntityManagerFactoryBean factory = new LocalContainerEntityManagerFactoryBean();
factory.setDataSource(dataSource()); //Get data source config here!
factory.setJpaVendorAdapter(jpaVendorAdapter());
factory.setPackagesToScan("com.globati.model");
return factory;
}
}
But I get this error.
java.lang.IllegalStateException: Failed to load ApplicationContext ...
Caused by:
org.springframework.beans.factory.NoSuchBeanDefinitionException: No
qualifying bean found for dependency
[com.globati.repository.DealRepository]: expected at least 1 bean
which qualifies as autowire candidate. Dependency annotations:
{#org.springframework.beans.factory.annotation.Autowired(required=true)}
Any suggestions for how I can successfully do unit testing with spring data, junit and with my service and repositories would be greatly appreciated. Thanks!
For a repository bean to be injected,
You need to enable Repositories, using one of the spring-data annotations. So add #Enable*Repositories to your configuration class
You also need dB factories and other related beans configured. I am using Mongo and I have mongoDbFactory bean configured
And for the most part your test configuration should look like your main configuration except for unnecessary bean replaced by mock implementations
UPDATE
Here is my code (sorry mine is in mongo, I think you can relate)
#Configuration
#WebAppConfiguration
#ComponentScan(basePackages = "com.amanu.csa",
excludeFilters = #ComponentScan.Filter(type = FilterType.ASSIGNABLE_TYPE, value = WebConfig.class))
#EnableMongoRepositories(repositoryImplementationPostfix = "CustomImpl")
class TestConfig {
#Bean
Mongo mongo() throws Exception {
return new MongoClient("localhost")
}
#Bean
MongoDbFactory mongoDbFactory() throws Exception {
return new SimpleMongoDbFactory(mongo(), "csa_test")
}
#Bean
MongoTemplate mongoTemplate() throws Exception {
MongoTemplate template = new MongoTemplate(mongoDbFactory())
template.setWriteResultChecking(WriteResultChecking.EXCEPTION)
return template
}
}
That is my test config file... As you can see it explicitly excludes my main configuration file.
#ContextConfiguration(classes = TestConfig)
#RunWith(SpringRunner.class)
class OrganizationServiceTest {
#Autowired
OrganizationService organizationService
#Test
void testRegister() {
def org = new Organization()
//...
organizationService.register(org)
// ...
}
And that is my test class. It refers to the test config, and I recommend using named config classes. You can put common options onto a super class and extend those and use them for your tests.
I hope this helps
You can try to add
#ActiveProfiles("your spring profile")
In addition I would suggest to use an embedded test db like flapdoodle (https://github.com/flapdoodle-oss/de.flapdoodle.embed.mongo)
You could either :
Mock your repository methods (Using Mockito for example)
Using an embedded database for your unit tests

Categories