MVC javafx Application, when to inject model - model-view-controller

I've got an application which i try to keep the mvc rule. I've got a model
public class CarTableView {
SimpleIntegerProperty id = new SimpleIntegerProperty();
SimpleStringProperty brand = new SimpleStringProperty();
SimpleStringProperty engine = new SimpleStringProperty();
SimpleBooleanProperty navi = new SimpleBooleanProperty();
SimpleBooleanProperty available = new SimpleBooleanProperty();
SimpleDoubleProperty liters = new SimpleDoubleProperty();
SimpleIntegerProperty power = new SimpleIntegerProperty();
ObservableList<CarTableView> observableList = FXCollections.observableArrayList();
public CarTableView()
{
}
public CarTableView(int id,String brand,String engine,Boolean navi,Boolean available,double liters,int power)
{
this.id.set(id);
this.brand.set(brand);
this.engine.set(engine);
this.navi.set(navi);
this.available.set(available);
this.liters.set(liters);
this.power.set(power);
}
Which I need to use in my two controllers in order to get referance to ObservableList. And here is where lie my problem. Where to create the Model? I need him to be created before the initialize() method will invoked
public class MainController {
private CarTableView model = new CarTableView();
#FXML
private Button addVehicleButton;
#FXML
private Button showClientDatabaseButton;
#FXML
public void initialize()
{
(...)
model.getObservableList().add(new CarTableView(213,"FIAT","1.9 JTD",true,true,32.4,132)); // HERE I use model (it's fine here because its created in this class)
tableView.setItems(model.getObservableList());
#FXML
public void addNewVehicleButtonClicked() throws IOException
{
FXMLLoader loader = new FXMLLoader(getClass().getResource("/fxml/AddNewCar.fxml"));
Stage stage = new Stage();
Scene scene = new Scene((Pane)loader.load());
stage.setScene(scene);
stage.show();
AddNewCarController addNewCarController = loader.getController();
addNewCarController.initData(model); // HERE i try to initialize model in second controller
}
But I need him also in initialize() method from another controller
public class AddNewCarController {
private ObservableList<String> choiceBoxList = FXCollections.observableArrayList("YES","NO");
#FXML
public void initialize()
{
autoIncrementChekBox.setSelected(true);
if(autoIncrementChekBox.isSelected())
{
idTextField.setText(Integer.toString((model.getObservableList().size()+1))); // HERE I NEED HIM TOO!! But it is null... he havent been load yet!
idTextField.setDisable(true);
}
comboBox.setItems(choiceBoxList);
comboBox.setValue("YES");
}
In this situation even if I will pass the data throw function initModel() in second controller. Where I should have create model, and pass to both controller?
I will also add my Main() file to clear situation
public class Main extends Application {
public static void main(String[] args) {
launch(args);
}
#Override
public void start(Stage stage) throws Exception {
stage.setTitle("Managment System");
FXMLLoader loader = new FXMLLoader(getClass().getResource("/fxml/Main.fxml"));
Scene scene = new Scene((Pane)loader.load(),1800,900);
MainController mainController = loader.getController();
stage.setScene(scene);
stage.show();
}
}

You can create the controller so that the model is passed to the constructor:
public class MainController {
private final CarTableView model ;
public MainController(CarTableView model) {
this.model = model ;
}
// existing code ...
}
To use this version of the controller, do
public class Main extends Application {
public static void main(String[] args) {
launch(args);
}
#Override
public void start(Stage stage) throws Exception {
stage.setTitle("Managment System");
FXMLLoader loader = new FXMLLoader(getClass().getResource("/fxml/Main.fxml"));
CarTableView model = new CarTableView();
MainController mainController = new MainController(model);
loader.setController(mainController);
Scene scene = new Scene((Pane)loader.load(),1800,900);
stage.setScene(scene);
stage.show();
}
}
And remove the fx:controller attribute from the FXML file.
You can then do the same when you load the other FXML file, using the same instance of the model.
A related approach is to use a controller factory:
CarTableView model = new CarTableView() ;
Callback<Class<?>, Object> controllerFactory = controllerType -> {
if (controllerType == MainController.class) {
return new MainController(model);
} else {
throw new IllegalStateException("Unexpected controller class: "+controllerType.getName());
}
};
FXMLLoader loader = new FXMLLoader(getClass().getResource("/fxml/Main.fxml"));
loader.setControllerFactory(controllerFactory);
Scene scene = new Scene(loader.load(), 1800, 900);
// ...
In this version, you keep the fx:controller attribute in the FXML file.
The basic idea here is that the controller factory is a function that maps the type of the controller to the controller instance, so you can use the controller factory to configure how the controller is created. You can use reflection to make this reusable (get the constructors for the class, check if any of them take a single parameter whose type is the model type, and invoke that constructor if so, otherwise just invoke the default constructor).
You can also use this technique to use a dependency injection framework such as Spring to create your controllers: see Dependency Injection and JavaFX. This, of course, means you can use Spring to inject the model into the controllers, which becomes very nice.
Finally, note that there is a JavaFX-specific dependency injection framework, afterburner.fx. This basically uses this technique under the hood to allow you to simply use javax.inject annotations directly in the controllers, so you just have to annotate the model in order to automatically inject it. If you have a medium-large scale application with lots of injection required, this is a very good option.

Related

Adding custom logic in Odata Create Entity call in java code

In the project am using olingo 2.0.12 jar in the java code.
During the create Entity service call ,
Is there a way to check for which entity data insert requested and,
Alter column values / append new column values before data persisted?
Is there a way to add above?
Code snippet given below,
public class A extends ODataJPADefaultProcessor{
#Override
public ODataResponse createEntity(final PostUriInfo uriParserResultView, final InputStream content,
final String requestContentType, final String contentType) throws ODataJPAModelException,
ODataJPARuntimeException, ODataNotFoundException, EdmException, EntityProviderException {
// Need to check the entity name and need to alter/add column values
}
}
Yes one of the possible ways would be to create your own CustomODataJPAProcessor which extends ODataJPADefaultProcessor.
You will have to register this in JPAServiceFactory by overriding the method
#Override
public ODataSingleProcessor createCustomODataProcessor(ODataJPAContext oDataJPAContext) {
return new CustomODataJPAProcessor(this.oDataJPAContext);
}
Now Olingo will use CustomODataJPAProcessor which can implement the following code to check the entities and transform them if needed
Sample code of CustomODataJPAProcessor
public class CustomODataJPAProcessor extends ODataJPADefaultProcessor {
Logger LOG = LoggerFactory.getLogger(this.getClass());
public CustomODataJPAProcessor(ODataJPAContext oDataJPAContext) {
super(oDataJPAContext);
}
#Override
public ODataResponse createEntity(final PostUriInfo uriParserResultView, final InputStream content,
final String requestContentType, final String contentType) throws ODataException {
ODataResponse oDataResponse = null;
oDataJPAContext.setODataContext(getContext());
InputStream forwardedInputStream = content;
try {
if (uriParserResultView.getTargetEntitySet().getName().equals("Students")) {
LOG.info("Students Entity Set Executed");
if (requestContentType.equalsIgnoreCase(ContentType.APPLICATION_JSON.toContentTypeString())) {
#SuppressWarnings("deprecation")
JsonElement elem = new JsonParser().parse(new InputStreamReader(content));
Gson gson = new GsonBuilder().setFieldNamingPolicy(FieldNamingPolicy.UPPER_CAMEL_CASE).create();
Student s = gson.fromJson(elem, Student.class);
// Change some values
s.setStudentID("Test" + s.getStudentID());
forwardedInputStream = new ByteArrayInputStream(gson.toJson(s).getBytes());
}
}
Object createdJpaEntity = jpaProcessor.process(uriParserResultView, forwardedInputStream,
requestContentType);
oDataResponse = responseBuilder.build(uriParserResultView, createdJpaEntity, contentType);
} catch (JsonIOException | JsonSyntaxException e) {
throw new RuntimeException(e);
} finally {
close();
}
return oDataResponse;
}
}
In Summery
Register your custom org.apache.olingo.odata2.service.factory Code Link
Create your own CustomODataJPAProcessor Code Link
Override createCustomODataProcessor in JPAServiceFactory to use the custom processor Code Link

Vaadin Spring Boot #autowired field returns null in a view

First off, please excuse my question due to my being new to spring boot ecosystem. In my application, I've a vaadin page, where I want to submit user details to DB, using repository. In my view class, I've added them as #autowired fields, however, during the runtime, I see that their values are run so the operation fails. I know that to benefit from #autowired, the instances should not be created newly during constructing but I couldn't figure out how I should do it on my own. Here are my classes:
#SuppressWarnings("serial")
public class LoginAwareComposite extends Composite<Div> {
#Autowired
private ApplicationEventPublisher publisher;
public LoginAwareComposite() {
}
#Override
protected void onAttach(AttachEvent event) {
super.onAttach(event);
UserCredentials userPrincipal = UI.getCurrent().getSession().getAttribute(UserCredentials.class);
if (userPrincipal != null) {
// --- NOT LOGGED IN
UI.getCurrent().navigate(AddressBookManagementView.class);
}
}
}
#SuppressWarnings("serial")
#Route(value = "account")
#Theme(value = Lumo.class, variant = Lumo.LIGHT)
public class AddressBookManagementView extends LoginAwareComposite {
private VerticalLayout pageLayout = new VerticalLayout();
public AddressBookManagementView() {
getContent().setSizeFull();
getContent().add(initPage());
}
private Component initPage() {
pageLayout.getStyle().set("padding-left", "0px");
pageLayout.getStyle().set("padding-bottom", "0px");
pageLayout.getStyle().set("padding-right", "0px");
pageLayout.getStyle().set("overflow", "auto");
pageLayout.setSizeFull();
pageLayout.add(new HeaderLayout(), new BodyLayout(), new FooterLayout());
return pageLayout;
}
}
#SuppressWarnings("serial")
#SpringComponent
public class BodyLayout extends VerticalLayout {
// some fields
#Autowired
EmailRepository emailRepository;
#Autowired
FaxRepository faxRepository;
public BodyLayout() {
init(); //this function inits the view, and eventually inits the on click event for submit button , which then calls my function
}
private void myFunction() {
//here i use the repository entities but they do return null although they are autowired
}
So what happens is, in BodyLayout's constructor we call init() function which is used to init the layout and give functionality buttons etc, one of subfunctions inside the init method gives functionality to submit button using myFunction. MyFuction uses the repository entity but it returns null.
Since you are using springboot with vaadin ensure the following :
Make sure that the #Repository annotation is used on your repository interfaces like on EmailRepository.
Try using constructor injection for your repository classes like :
Try like below :
#SuppressWarnings("serial")
#SpringComponent
#UIScope
public class BodyLayout extends VerticalLayout {
// some fields
private final EmailRepository emailRepository;
private final FaxRepository faxRepository;
#Autowired
public BodyLayout(EmailRepository emailRepository, FaxRepository faxRepository) {
this.emailRepository = emailRepository;
this.faxRepository = faxRepository;
init(); //this function inits the view, and eventually inits the on click event for submit button , which then calls my function
}
private void myFunction() {
//here i use the repository entities but they do return null although they are autowired
}
I was able to get it working as follows:
#SuppressWarnings("serial")
#Route(value = "account")
#Theme(value = Lumo.class, variant = Lumo.LIGHT)
#UIScope
#SpringComponent
public class AddressBookManagementView extends LoginAwareComposite {
private VerticalLayout pageLayout = new VerticalLayout();
#Autowired
BodyLayout bodyLayout;
public AddressBookManagementView(BodyLayout bodyLayout) {
this.bodyLayout = bodyLayout;
getContent().setSizeFull();
getContent().add(initPage());
}
private Component initPage() {
pageLayout.getStyle().set("padding-left", "0px");
pageLayout.getStyle().set("padding-bottom", "0px");
pageLayout.getStyle().set("padding-right", "0px");
pageLayout.getStyle().set("overflow", "auto");
pageLayout.setSizeFull();
pageLayout.add(new HeaderLayout(), bodyLayout, new FooterLayout());
return pageLayout;
}
Then BodyLayout is
#SuppressWarnings("serial")
#UIScope
#SpringComponent
public class BodyLayout extends VerticalLayout {
private final EmailRepository emailRepository;
private final FaxRepository faxRepository;
#Autowired
public BodyLayout(EmailRepository emailRepository, FaxRepository faxRepository) {
this.emailRepository = emailRepository;
this.faxRepository = faxRepository;
init();
}
Roughly only #Route, layouts, and the vaadin init listener takes part in automatic dependency injection (that is: the vaadin spring integration asks the spring context to build them). If you do new MyClass() it never takes part in DI. Using field based injection with #Autowired hides this problem - so using constructor based injection is the "industry standard". The other way around is to not build your own instances, if you want to take part in DI but ask the spring context to build an instance for you.

How to set a tableview in JavaFX in initialize method

Hi I'm new to JavaFX and want to populate a tableview with data
I use not the programmatic way I create the tableview with scene builder and have a controller file . I have a data file for the getters and setters already and created a observable list
public class Controller implements Initializable {
#FXML TableView<Bew> tableV;
#FXML TableColumn<Bew, String> nameCol =new TableColumn<>("Name");
#FXML TableColumn<Bew, String> dateCol =new TableColumn<>("Datum");
#FXML TableColumn<Bew, String> actionCol =new TableColumn<>("Aktivität");
#FXML TableColumn<Bew, String> infoCol =new TableColumn<>("Info");
#Override
public void initialize(URL url, ResourceBundle resourceBundle) {
SQLiteDB DB = new SQLiteDB();
ObservableList<Bew> list = DB.getData();
tableV.setItems(list);
}
public ObservableList<Bew> getData() {
ObservableList<Bew> bewList = FXCollections.observableArrayList();
bewList.add(new Bew("Rita", "01.12.1920", "Gespräch", "gud"));
bewList.add(new Bew("Marc", "14.03.1930", "Spaziergang", "gud"));
bewList.add(new Bew("Peter", "27.01.1901", "dfdsfdsfdsfdsf", "gud"));
bewList.add(new Bew("John", "12.12.1912", "dfdsfdsfdsfdsf", "gud"));
return bewList;
}
When I run the code no error appears but no data in tableview
Any help?

Vaadin: get reference of UI to change data

I want to change data inside a Vaadin UI. The change is invoked by a a rest call. There, i somehow need a reference to the UI class to call its method´, e.g. changeValue(string value).
I'm using vaadin-spring-boot-starter 1.0.0
Is that somehow possible?
EDIT: Another question now:
I was trying to do that Server Push, mentioned by #Eric, inside of a View, so that the view will get updated on a Broadcast message. However, this is not working (no exceptions, nothing to debug, just no updates in the view). This is what i do in my View:
#UIScope
#SpringView(name = LoadWebsiteView.VIEW_NAME)
#Push
public class LoadWebsiteView extends VerticalLayout implements View, Broadcaster.BroadcastListener {
...
#Autowired
public LoadWebsiteView(ScraperMainUI scraperMainUi) {
this.scraperMainUi = scraperMainUi;
Broadcaster.register(this);
initControlPane();
}
#Override
public void receiveBroadcast(String message) {
scraperMainUi.access(new Runnable() {
#Override
public void run() {
urlTxtField.setValue(message);
}
});
}
and here is the simple stuff i do in my restcontroller:
Broadcaster.broadcast(text);
What you are looking for is Vaadin's Push feature and a way to send a message to a list of registered "clients" (in this case, the Vaadin UIs who need to known about the changes).
You can read about Vaadin Push here: Enabling Server Push and also in the article Advanced Push
The Vaadin push function allows your server to force updates to the client instead of waiting on the browser to request again.
The message component simply acts as a way to tell subscribed UIs that there is an update they need to action.
This said, I have a project that does about the same as multiple users are actioning items and there are Spring scheduled tasks that also can effect changes the user needs to know about.
Note, the below examples are based on the examples available in Enabling Server Push article.
Broadcaster.java - Acts as the mechanism that registers instances to receive broadcasts and provides a facility to send broadcasts. In the below example, I have I have a class that represents a message (BroadcastMessage) but you could simplify it of course.
public class Broadcaster implements Serializable {
private static final long serialVersionUID = 3540459607283346649L;
static ExecutorService executorService = Executors.newSingleThreadExecutor();
private static LinkedList<BroadcastListener> listeners = new LinkedList<BroadcastListener>();
public interface BroadcastListener {
void receiveBroadcast(BroadcastMessage message);
}
public static synchronized void register(BroadcastListener listener) {
listeners.add(listener);
}
public static synchronized void unregister(BroadcastListener listener) {
listeners.remove(listener);
}
public static synchronized void broadcast(final BroadcastMessage message) {
for (final BroadcastListener listener: listeners)
executorService.execute(new Runnable() {
#Override
public void run() {
listener.receiveBroadcast(message);
}
});
}
}
Here is the class I defined for my BroadcastMessage. The idea is to have a way to denote what kind of message I have and also some payload in the form of a Map
public class BroadcastMessage implements Serializable {
private static final long serialVersionUID = 5637577096751222106L;
public BroadcastMessageType messageType;
public Map<String, String> params;
public BroadcastMessage() {
}
public BroadcastMessage(BroadcastMessageType messageType) {
this.messageType = messageType;
this.params = new HashMap<String, String>();
}
public BroadcastMessage(BroadcastMessageType messageType, Map<String, String> params) {
this.messageType = messageType;
this.params = params;
}
public BroadcastMessageType getMessageType() {
return messageType;
}
public void setMessageType(BroadcastMessageType messageType) {
this.messageType = messageType;
}
public Map<String, String> getParams() {
return params;
}
public void setParams(Map<String, String> params) {
this.params = params;
}
}
This is an example Vaadin UI that wants to listen for Broadcasts. Note the #Push annotation. Without this, the client will only refresh when the browser decides to. #Push makes it immediate**
#SpringComponent
#UIScope
#Push
#SpringView(name=TaskListComponent.NAME)
public class TaskListComponent extends MyCustomComponent implements Broadcaster.BroadcastListener, View {
/** PRUNED DOWN, TO DEMONSTRATE THE KEY CODE **/
// Register this window when we enter it
#Override
public void enter(ViewChangeEvent event) {
Broadcaster.register(this);
}
// Must also unregister when the UI expires
#Override
public void detach() {
Broadcaster.unregister(this);
super.detach();
}
// Receive a broadcast
#Override
public void receiveBroadcast(BroadcastMessage message) {
getUI().access(new Runnable() {
#Override
public void run() {
// DO WHATEVER YOU NEED TO DO HERE.
// I CALLED INITIALIZE BUT IT COULD BE
// JUST YOU FIELD UPDATE
if ( message.getMessageType().equals(BroadcastMessageType.REFRESH_TASK_LIST) )
initialize();
}
});
}
}
To send a message from your rest interface:
Broadcaster.broadcast(
new BroadcastMessage(
BroadcastMessageType.AUTO_REFRESH_LIST
)
);
Hope this helps! :)

Spring #Value property for custom class

Is it possible to use Spring's #Value annotation to read and write property values of a custom class type?
For example:
#Component
#PropertySource("classpath:/data.properties")
public class CustomerService {
#Value("${data.isWaiting:#{false}}")
private Boolean isWaiting;
// is this possible for a custom class like Customer???
// Something behind the scenes that converts Custom object to/from property file's string value via an ObjectFactory or something like that?
#Value("${data.customer:#{null}}")
private Customer customer;
...
}
EDITED SOLUTION
Here is how I did it using Spring 4.x APIs...
Created new PropertyEditorSupport class for Customer class:
public class CustomerPropertiesEditor extends PropertyEditorSupport {
// simple mapping class to convert Customer to String and vice-versa.
private CustomerMap map;
#Override
public String getAsText()
{
Customer customer = (Customer) this.getValue();
return map.transform(customer);
}
#Override
public void setAsText(String text) throws IllegalArgumentException
{
Customer customer = map.transform(text);
super.setValue(customer);
}
}
Then in application's ApplicationConfig class:
#Bean
public CustomEditorConfigurer customEditorConfigurer() {
Map<Class<?>, Class<? extends PropertyEditor>> customEditors =
new HashMap<Class<?>, Class<? extends PropertyEditor>>(1);
customEditors.put(Customer.class, CustomerPropertiesEditor.class);
CustomEditorConfigurer configurer = new CustomEditorConfigurer();
configurer.setCustomEditors(customEditors);
return configurer;
}
Cheers,
PM
You have to create a class extending PropertyEditorSupport.
public class CustomerEditor extends PropertyEditorSupport {
#Override
public void setAsText(String text) {
Customer c = new Customer();
// Parse text and set customer fields...
setValue(c);
}
}
It's possible but reading Spring documentation. You could see this example:
Example usage
#Configuration
#PropertySource("classpath:/com/myco/app.properties")
public class AppConfig {
#Autowired
Environment env;
#Bean
public TestBean testBean() {
TestBean testBean = new TestBean();
testBean.setName(env.getProperty("testbean.name"));
return testBean;
}
}
See details here
Spring can read properties and load them directly into a class.
Moreover, you can add #ConfigurationProperties(prefix = "data") on top of the class, instead of wiring each nested property one by one, by making the code cleaner.
Given all that, here is the final example with explanations:
// File: CustomerConfig.java
#Configuration
// Set property source file path (optional)
#PropertySource("classpath:/data.properties")
// Put prefix = "data" here so that Spring read properties under "data.*"
#ConfigurationProperties(prefix = "data")
public class CustomerConfig {
// Note: Property name here is the same as in the file (data.customer)
// Spring will automatically read and put "data.customer.*" properties into this object
private Customer customer;
// Other configs can be added here too... without wiring one-by-one
public setCustomer(Customer customer){
this.customer = customer;
}
public getCustomer(){
return this.customer;
}
}
That's it, now you have "data.customer.*" properties, loaded and accessible via CustomerConfig.getCustomer().
To integrate it into your service (based on your example code):
// File: CustomerService.java
#Component
#PropertySource("classpath:/data.properties")
public class CustomerService {
#Value("${data.isWaiting:#{false}}")
private Boolean isWaiting;
#Autowired // Inject configs, either with #Autowired or using constructor injection
private CustomerConfig customerConfig;
public void myMethod() {
// Now its available for use
System.out.println(customerConfig.getCustomer().toString());
}
}
This way no "magical hack" is required to read configs into a class.
Take a look at the #ConfigurationProperties documentation/examples, and this post for more useful info.
Note: I'd suggest against using PropertyEditorSupport, since
a) it was built for different purpose, may change in future by breaking the code
b) it requires manual "handling" code inside => possible bugs
Instead, use what was built right for that purpose (Spring already has it), in order to both make the code easier to understand, and to gain possible inner improvements/optimizations which might be done in the future (or present).
Further improvements: Your CustomerService seems to be cluttered with configs (#PropertyService) too. I'd suggest reading those properties via another class too (similarly) then wiring that class here, instead of doing all in the CustomerService.
If you want to use it with lists, there is a workaround using array instead.
Define your property as Customer[] instead of List then:
in ApplicationConfig class:
#Bean
public CustomEditorConfigurer customEditorConfigurer() {
Map<Class<?>, Class<? extends PropertyEditor>> customEditors =
new HashMap<Class<?>, Class<? extends PropertyEditor>>(1);
customEditors.put(Customer.class, CustomerPropertiesEditor.class);
customEditors.put(Customer[].class, CustomerPropertiesEditor.class);
CustomEditorConfigurer configurer = new CustomEditorConfigurer();
configurer.setCustomEditors(customEditors);
return configurer;
}
In CustomerEditor:
public class CustomerEditor extends PropertyEditorSupport {
public static final String DEFAULT_SEPARATOR = ",";
#Override
public void setAsText(String text) {
String[] array = StringUtils.delimitedListToStringArray(text, this.separator);
if (this.emptyArrayAsNull && array.length == 0) {
super.setValue((Object) null);
} else {
if (this.trimValues) {
array = StringUtils.trimArrayElements(array);
}
// Convert String[] to Customer[]
super.setValue(...);
}
}
}
If you want to use an existing converter/constructor, you can just call it within your expression.
For example:
#Value("#{T(org.test.CutomerMap).transform('${serialized.customer}')}")
private Customer customer;

Resources