How to bind BeanItemContainer to Combobox - spring

I have BeanItemContainer, which i load from database via jdbc:
BeanItemContainer myBeans = new BeanItemContainer<>(MyBean.class, mybeanDao.findAll());
and this is how i attach it to combobox:
Combobox combo = new Combobox();
combobox.setContainerDataSource(myBeans);
So far, so good. I received what i want, but for now i have a problem -
How do i get actual id that has been selected? This must be synchronized (id selected in combobox is actual entry in database).
I have no idea, how to solve this problem
Please help
P.S MyBean class
public class MyBean {
private Long id;
private String field1;
*** getters /setters ***
and toString() {} method
}

Here is the code:
#Theme("mytheme")
public class MyUI extends UI {
#Override
protected void init(VaadinRequest vaadinRequest) {
final VerticalLayout layout = new VerticalLayout();
layout.setMargin(true);
layout.setSpacing(true);
setContent(layout);
BeanItemContainer myBeans = new BeanItemContainer<>(MyBean.class, getBeans());
ComboBox combo = new ComboBox();
combo.setContainerDataSource(myBeans);
combo.setItemCaptionMode(AbstractSelect.ItemCaptionMode.PROPERTY);
combo.setItemCaptionPropertyId("field");
combo.addValueChangeListener(new Property.ValueChangeListener() {
#Override
public void valueChange(Property.ValueChangeEvent event) {
MyBean bean = (MyBean) combo.getValue();
Notification notif = new Notification("Selected Bean Id: "+bean.getId(), Notification.Type.TRAY_NOTIFICATION);
notif.setPosition(Position.TOP_CENTER);
notif.show(Page.getCurrent());
}
});
layout.addComponent(combo);
}
#WebServlet(urlPatterns = "/*", name = "MyUIServlet", asyncSupported = true)
#VaadinServletConfiguration(ui = MyUI.class, productionMode = false)
public static class MyUIServlet extends VaadinServlet {
}
public class MyBean {
private Long id;
private String field;
public MyBean(Long id, String field) {
this.id = id;
this.field = field;
}
public Long getId() {
return id;
}
public String getField() {
return field;
}
}
public ArrayList<MyBean> getBeans() {
ArrayList<MyBean> beans = new ArrayList<>();
MyBean bean = new MyBean(1l, "Vikrant");
beans.add(bean);
bean = new MyBean(2l, "Rampal");
beans.add(bean);
bean = new MyBean(3l, "viky");
beans.add(bean);
return beans;
}
}

If I understood the question correctly combo.getValue() should give you the MyBean instance relative to the current selection (or null if no item is selected)

Related

Take the sum of the query result as separate data

Etity
#Entity
public class DateFMail {
#Id
private double balance;
public DateFMail() {
}
public DateFMail(double balance) {this.balance = balance;}
public DateFMail(DateFMail dateFMail) {
}
public double getBalance() { return balance;}
#Override
public String toString() {
return "DateFMail{" +
"balance=" + balance +
'}';
}
}
Service
public interface DateFMailService {
List<DateFMail> findAll();
}
Impl
#Service
public class DateFMailServiceImpl implements DateFMailService {
#Autowired
private DateFMailRepository mailRepository;
#Override
public List<DateFMail> findAll() {
return mailRepository.findAll();
}
}
Repository
#Repository
public interface DateFMailRepository extends JpaRepository<DateFMail, Long> {
#Query(value = "SELECT SUM(balance) \n" +
" FROM agents", nativeQuery = true)
List<DateFMail> findAll();
}
Mail Seder
#Service
public class EmailDos {
#Autowired
private JavaMailSender mailSender;
private DateFMailRepository mailRepository;
String fileDate1 = new SimpleDateFormat("dd.MM.yyyy").format(new Date());
LocalDate today = LocalDate.now();
String fileDate = (today.minusDays(1)).format(DateTimeFormatter.ofPattern("dd MMM"));
String fileDate2 = (today.minusMonths(1)).format(DateTimeFormatter.ofPattern("MMM"));
public void sendMailSum(String from, String to, String subject, String body, String fileToAttach) throws SQLException {
List<DateFMail> list = new ArrayList<>(mailRepository.findAll());
List<DateFMail> list1 = list.stream()
.map(DateFMail::new)
.collect(Collectors.toList());
System.out.println("sending email...................");
System.out.println(list1);
MimeMessagePreparator preparator = new MimeMessagePreparator() {
public void prepare(MimeMessage mimeMessage) throws Exception {
mimeMessage.setFrom(new InternetAddress(from));
mimeMessage.setRecipient(Message.RecipientType.TO, new InternetAddress(to));
mimeMessage.setSubject(subject);
mimeMessage.setText(body);
FileSystemResource file = new FileSystemResource(new File("C:...xlsx"));
MimeMessageHelper helper = new MimeMessageHelper(mimeMessage, true);
helper.setFrom("SomeAddress#gmail.com");
helper.setTo(InternetAddress.parse("SomeAddress#gmail.com"));
helper.setText("Good day!\nIn attachment payments for " + fileDate + " с 12.00-00.00" + "\nAmount for " + fileDate1 + list1);
helper.addAttachment("...xlsx", file);
mailSender.send(mimeMessage);
System.out.println("email Fab was successfully sent.....");
}
};
try {
mailSender.send(preparator);
} catch (MailException ex) {
System.err.println(ex.getMessage());
}
}
}
Controller
#Component
public class DateFMailController {
#Autowired
private DateFMailService mailService;
public void saveSum() throws IOException {
saveExcel(mailService.findAll(), "....xlsx");
}
private void saveExcel(List<DateFMail> list, String fileName) throws IOException {
Workbook workbook = new XSSFWorkbook();
CreationHelper createHelper = workbook.getCreationHelper();
Sheet sheet = workbook.createSheet("ECards");
sheet.autoSizeColumn(0);
Row header = sheet.createRow(0);
CellStyle headerStyle = workbook.createCellStyle();
headerStyle.setFillForegroundColor(IndexedColors.LIGHT_BLUE.getIndex());
headerStyle.setFillPattern(FillPatternType.SOLID_FOREGROUND);
XSSFFont font = ((XSSFWorkbook) workbook).createFont();
font.setFontName("Arial");
font.setFontHeightInPoints((short) 10);
font.setBold(true);
headerStyle.setFont(font);
Cell headerCell = header.createCell(0);
headerCell.setCellValue("Sum");
headerCell.setCellStyle(headerStyle);
CellStyle style = workbook.createCellStyle();
style.setWrapText(true);
int ix_row=2;
for (DateFMail dateFMail : list) {
Row row = sheet.createRow(ix_row);
Cell cell = row.createCell(0);
cell.setCellValue(dateFMail.getBalance());
cell.setCellStyle(style);
ix_row++;
}
FileOutputStream outputStream = new FileOutputStream(fileName);
workbook.write(outputStream);
workbook.close();
}
}
Save Runer
#Component
public class SaveCardsStartupRunner implements ApplicationRunner {
#Autowired
private ECardController eCardController;
private DateFMailController controller;
// #Autowired
// private EmailDos emailDos;
String fileDate1 = new SimpleDateFormat("dd.MM.yyyy").format(new Date());
LocalDate today = LocalDate.now();
String fileDate = (today.minusDays(1)).format(DateTimeFormatter.ofPattern("dd MMM"));
String fileDate2 = (today.minusMonths(1)).format(DateTimeFormatter.ofPattern("MMM"));
#Override
public void run(ApplicationArguments args) throws Exception {
eCardController.saveCards();
controller.saveSum();
}
}
I have corrected my question. I've pasted all the code here that pertains to my question. For starters, I would like to simply output the Query result of the repository to the console. But in the form that I just posted here, I get a NullPointerException error and says that in a part of the code: controller.saveSum (); - controller = null.
Create a PaymentService class which should contain the method getTotalPayment. Inject this class in EmailSend (tip: please change this class name from EmailSend to EmailSender as class names should be noun) class. And then in PaymentService Class you should interact Data Repository class. Call this getTotalPayment method from the EmailSend class.

How can I add data to CRUD in Vaadin by using setItems for grid?

I'm going to add data to a CRUD component in Vaadin. It's an easy question here.
But the issue I got is that I cannot add data to the CRUD by first getting the grid object and then set its items to it.
Here is my Vaadin class. This class begins first to get data from a JPA Spring database. OK. That's works. And the data is transfered into a collection named crudData. Then the crudData is beings set to crud.getGrid().setItems(crudData); and that's not working. I assume that if I get the grid from the CRUD, then I can set the grid items as well too and then they will show up on the CRUD....but no...
#Data
public class StocksCrud {
private Crud<StockNames> crud;
private List<StockNames> crudData;
private StockNamesRepository stockNamesRepository;
private CrudEditor<StockNames> createStocksEditor() {
TextField stockName = new TextField("Name of the stock");
FormLayout form = new FormLayout(stockName);
Binder<StockNames> binder = new Binder<>(StockNames.class);
binder.bind(stockName, StockNames::getStockName, StockNames::setStockName);
return new BinderCrudEditor<>(binder, form);
}
public StocksCrud(StockNamesRepository stockNamesRepository) {
this.stockNamesRepository = stockNamesRepository;
// Fill the crud
crudData = new ArrayList<StockNames>();
for(StockNames stockName: stockNamesRepository.findAll()) {
crudData.add(new StockNames(stockName.getId(), stockName.getStockName()));
}
// Crate crud table
crud = new Crud<>(StockNames.class, createStocksEditor());
crud.getGrid().setItems(crudData); // This won't work
crud.addSaveListener(e -> saveStock(e.getItem()));
crud.addDeleteListener(e -> deleteStock(e.getItem()));
crud.getGrid().removeColumnByKey("id");
crud.addThemeVariants(CrudVariant.NO_BORDER);
}
private void deleteStock(StockNames stockNames) {
boolean exist = stockNamesRepository.existsBystockName(stockNames.getStockName());
if(exist == true) {
crudData.remove(stockNames);
stockNamesRepository.delete(stockNames);
}
}
private void saveStock(StockNames stockNames) {
System.out.println(stockNames == null);
System.out.println(stockNamesRepository == null);
boolean exist = stockNamesRepository.existsBystockName(stockNames.getStockName());
if(exist == false) {
crudData.add(stockNames);
stockNamesRepository.save(stockNames);
}
}
}
Here is my error output:
java.lang.ClassCastException: com.vaadin.flow.component.crud.CrudFilter cannot be cast to com.vaadin.flow.function.SerializablePredicate
I know that there is a way to set data to CRUD in Vaadin, by using a data provider class. But I don't want to use that. It's....to much code. I want to keep it clean and write less code in Java. Example here at the bottom: https://vaadin.com/components/vaadin-crud/java-examples
#Entity
#Data
#NoArgsConstructor
public class StockNames implements Cloneable{
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private int id;
private String stockName;
public StockNames(int id, String stockName) {
this.id = id;
this.stockName = stockName;
}
}
Update:
This is my code now
#Data
public class StocksCrud {
private Crud<StockNames> crud;
private List<StockNames> crudData;
private StockNamesRepository stockNamesRepository;
private CrudEditor<StockNames> createStocksEditor() {
TextField stockName = new TextField("Name of the stock");
FormLayout form = new FormLayout(stockName);
Binder<StockNames> binder = new Binder<>(StockNames.class);
binder.bind(stockName, StockNames::getStockName, StockNames::setStockName);
return new BinderCrudEditor<>(binder, form);
}
public StocksCrud(StockNamesRepository stockNamesRepository) {
this.stockNamesRepository = stockNamesRepository;
// Fill the crud
crudData = new ArrayList<StockNames>();
for(StockNames stockName: stockNamesRepository.findAll()) {
crudData.add(new StockNames(stockName.getId(), stockName.getStockName()));
}
// Create grid
Grid<StockNames> grid = new Grid<StockNames>();
grid.setItems(crudData);
// Crate crud table
crud = new Crud<>(StockNames.class, createStocksEditor());
crud.setGrid(grid);
crud.addSaveListener(e -> saveStock(e.getItem()));
crud.addDeleteListener(e -> deleteStock(e.getItem()));
//crud.getGrid().removeColumnByKey("id");
crud.addThemeVariants(CrudVariant.NO_BORDER);
}
private void deleteStock(StockNames stockNames) {
boolean exist = stockNamesRepository.existsBystockName(stockNames.getStockName());
if(exist == true) {
crudData.remove(stockNames);
stockNamesRepository.delete(stockNames);
}
}
private void saveStock(StockNames stockNames) {
System.out.println(stockNames == null);
System.out.println(stockNamesRepository == null);
boolean exist = stockNamesRepository.existsBystockName(stockNames.getStockName());
if(exist == false) {
crudData.add(stockNames);
stockNamesRepository.save(stockNames);
}
}
}
Update 2:
This gives an error.
// Create grid
Grid<StockNames> grid = new Grid<StockNames>();
StockNames s1 = new StockNames(1, "HELLO");
crudData.add(s1);
grid.setItems(crudData);
// Crate crud table
crud = new Crud<>(StockNames.class, createStocksEditor());
crud.setGrid(grid);
crud.addSaveListener(e -> saveStock(e.getItem()));
crud.addDeleteListener(e -> deleteStock(e.getItem()));
crud.getGrid().removeColumnByKey(grid.getColumns().get(0).getKey());
crud.addThemeVariants(CrudVariant.NO_BORDER);
The error is:
java.lang.IndexOutOfBoundsException: Index: 0, Size: 0'
What? I just added a object.
Assigning grid fixes the issue
Grid<StockNames> grid=new Grid<>(StockNames.class);
crud = new Crud<>(StockNames.class,grid, createStocksEditor());
In your code example you are relying on default implementation provided by Crud, thus CrudGrid is getting created. Its setDataProvider returns DataProvider<E,CrudFilter>, whereas Grid's DataProvider is of type: AbstractDataProvider<T, SerializablePredicate<T>> (This is because you are using ListDataProvider, which extends AbstractDataProvider<T, SerializablePredicate<T>>). This is what error states:
java.lang.ClassCastException: com.vaadin.flow.component.crud.CrudFilter cannot be cast to com.vaadin.flow.function.SerializablePredicate
So if you want to assign values via grid- you would first need to create one. Otherwise, as shown in the docs you could provide a custom dataprovider: PersonDataProvider
Update
This is an example code I am using. Adding a new item in Crud works, after I have added a no-args constructor to the bean:
import java.util.Random;
public class StockNames implements Cloneable{
Random rnd=new Random();
private int id;
private String stockName;
public StockNames(){
//You will an id generated automatically for you, but here is just an example
id=rnd.nextInt(12000);
}
public StockNames(int id, String stockName) {
this.id = id;
this.stockName = stockName;
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getStockName() {
return stockName;
}
public void setStockName(String stockName) {
this.stockName = stockName;
}
}
and the StockCrud class:
import com.vaadin.flow.component.crud.*;
import com.vaadin.flow.component.formlayout.FormLayout;
import com.vaadin.flow.component.grid.Grid;
import com.vaadin.flow.component.orderedlayout.VerticalLayout;
import com.vaadin.flow.component.textfield.TextField;
import com.vaadin.flow.data.binder.Binder;
import com.vaadin.flow.router.Route;
import java.util.ArrayList;
import java.util.List;
#Route("crudLayout")
public class StockCrud extends VerticalLayout {
private Crud<StockNames> crud;
private List<StockNames> crudData;
private CrudEditor<StockNames> createStocksEditor() {
TextField stockName = new TextField("Name of the stock");
FormLayout form = new FormLayout(stockName);
Binder<StockNames> binder = new Binder<>(StockNames.class);
binder.bind(stockName, StockNames::getStockName, StockNames::setStockName);
return new BinderCrudEditor<>(binder, form);
}
public StockCrud() {
// Fill the crud
crudData = new ArrayList<StockNames>();
for(int i=0;i<150;i++) {
crudData.add(new StockNames(i,"Name " + i));
}
// Crate crud table
Grid<StockNames> grid=new Grid<>(StockNames.class);
crud = new Crud<>(StockNames.class,grid, createStocksEditor());
//((CrudGrid )crud.getGrid()).setItems(crudData);
crud.getGrid().setItems(crudData); // This won't work
crud.addSaveListener(e -> saveStock(e.getItem()));
crud.addDeleteListener(e -> deleteStock(e.getItem()));
// crud.getGrid().removeColumnByKey("id");
crud.addThemeVariants(CrudVariant.NO_BORDER);
add(crud);
}
private void deleteStock(StockNames stockNames) {
// if(crudData.contains(stockNames)) {
crudData.remove(stockNames);
//}
}
private void saveStock(StockNames stockNames) {
System.out.println(stockNames == null);
if(!crudData.contains(stockNames)) {
crudData.add(stockNames);
}
}
}

How to reference a properties value inside the schema attribute of an entity?

There is an entity :
#Entity
#Table(name = "ITMMASTER" , schema = "TOMCTB")
public class Article {
#Id
#Column(name = "ITMREF_0")
private String code_article;
#Column(name = "ACCCOD_0")
private String acccod;
public String getCode_article() {
return code_article;
}
public void setCode_article(String code) {
this.code_article = code;
}
public String getAcccod() {
return acccod;
}
public void setAcccod(String acccod) {
this.acccod = acccod;
}
}
I want to make the schema attribute to be dynamic depending on a properties file property value , for example : env.schema = TOMEXPL.
How to achieve that ?
I didn't tried it but I guess this should work.
public class DynamicNamingStrategy extends DefaultNamingStrategy {
#Value("db.table.name")
private String name;
#Override
public String tableName(String tableName) {
return name;
}
...
}
SessionFactory sessionFactory;
Configuration config = new AnnotationConfiguration()
.configure("hibernate.cfg.xml")
.setNamingStrategy( new DynamicNamingStrategy() );
sessionFactory = config.buildSessionFactory();
session = sessionFactory.openSession();

JPA repository fail with good name of property and works with wrong name

I have a class with property named isChecked with is boolean type. In Jpa repository I wrote a method to find all rows which has isChecked = false;
public interface ReservationReminderRepository extends JpaRepository<ReservationReminder, Integer> {
ReservationReminder findByReservationReminderId(Integer id);
//#Query("select r from ReservationReminder r where r.isChecked = :checked")
List<ReservationReminder> findByChecked(boolean checked);
}
While I tried to call a method findByChecked() in Jpa reporistory everything works, but when I tried to run a method with the proper named of property - as it is in jpa doc findByIsChecked() I got strange fails:
Caused by: java.lang.IllegalArgumentException: Unable to locate Attribute with the the given name [isChecked] on this ManagedType [com.carwash.domains.ReservationReminder]
at org.hibernate.jpa.internal.metamodel.AbstractManagedType.checkNotNull(AbstractManagedType.java:128)
at org.hibernate.jpa.internal.metamodel.AbstractManagedType.getAttribute(AbstractManagedType.java:113)
at org.springframework.data.jpa.repository.query.QueryUtils.toExpressionRecursively(QueryUtils.java:566)
at org.springframework.data.jpa.repository.query.JpaQueryCreator$PredicateBuilder.getTypedPath(JpaQueryCreator.java:334)
at org.springframework.data.jpa.repository.query.JpaQueryCreator$PredicateBuilder.build(JpaQueryCreator.java:277)
at org.springframework.data.jpa.repository.query.JpaQueryCreator.toPredicate(JpaQueryCreator.java:182)
at org.springframework.data.jpa.repository.query.JpaQueryCreator.create(JpaQueryCreator.java:109)
at org.springframework.data.jpa.repository.query.JpaQueryCreator.create(JpaQueryCreator.java:49)
at org.springframework.data.repository.query.parser.AbstractQueryCreator.createCriteria(AbstractQueryCreator.java:109)
at org.springframework.data.repository.query.parser.AbstractQueryCreator.createQuery(AbstractQueryCreator.java:88)
at org.springframework.data.repository.query.parser.AbstractQueryCreator.createQuery(AbstractQueryCreator.java:73)
at org.springframework.data.jpa.repository.query.PartTreeJpaQuery$QueryPreparer.<init>(PartTreeJpaQuery.java:118)
at org.springframework.data.jpa.repository.query.PartTreeJpaQuery$CountQueryPreparer.<init>(PartTreeJpaQuery.java:241)
at org.springframework.data.jpa.repository.query.PartTreeJpaQuery.<init>(PartTreeJpaQuery.java:68)
at org.springframework.data.jpa.repository.query.JpaQueryLookupStrategy$CreateQueryLookupStrategy.resolveQuery(JpaQueryLookupStrategy.java:103)
... 104 more
Can anyone tell me why I received that kind of fail? How the method name would looks like when I'd like to check with property checkedDate?
package com.carwash.domains;
import javax.persistence.*;
import java.util.Date;
/**
* Created by mbi on 01.03.2017.
*/
#Entity
public class ReservationReminder {
private int reservationReminderId;
private Reservation reservation;
private boolean isChecked;
private Date checkedDate;
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
public int getReservationReminderId() {
return reservationReminderId;
}
public void setReservationReminderId(int reservationReminderId) {
this.reservationReminderId = reservationReminderId;
}
#OneToOne(mappedBy = "reservationReminder")
public Reservation getReservation() {
return reservation;
}
public void setReservation(Reservation reservation) {
this.reservation = reservation;
}
public boolean getChecked() {
return isChecked;
}
public void setChecked(Boolean checked) {
isChecked = checked;
}
public Date getCheckedDate() {
return checkedDate;
}
public void setCheckedDate(Date checkedDate) {
this.checkedDate = checkedDate;
}
#Override
public String toString() {
return "ReservationReminder{" +
"reviewId=" + reservationReminderId +
", isChecked=" + isChecked +
", checkedDate=" + checkedDate +
'}';
}
public ReservationReminder() {
}
public ReservationReminder(Boolean isChecked, Date checkedDate) {
this.isChecked = isChecked;
this.checkedDate = checkedDate;
}
public ReservationReminder(int reservationReminderId, Reservation reservation, boolean isChecked, Date checkedDate) {
this.reservationReminderId = reservationReminderId;
this.reservation = reservation;
this.isChecked = isChecked;
this.checkedDate = checkedDate;
}
}
It seems that the problem is related to the naming of that property.
As you are telling Spring to look for findByChecked and the property name is isChecked.
You can try to use findByIsChecked and change the getter to isChecked.
But actually i would change the property to checked, getter to isChecked and leave the jpa query method as it is.

#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.

Resources