This tutorial shows how to invoke REST services using JavaFX API. For the purpose of it, we will use the KitchenSink basic REST service
and engineer a JavaFX client for it.
A pre-requisite of this tutorial is that you have deployed the Kitchensink basic demo application which allows you to create a compliant Java EE 6 application using a mix of different technologies. You can download the Maven project from here: https://github.com/jboss-jdf/jboss-as-quickstart/tree/master/kitchensink
The next requisite is that you have got installed JavaFx on your machine: this is automatically included if you have got Java SE 1.7. If you are running a Java 1.6 platform, you have to download JavaFX separately http://www.oracle.com/technetwork/java/javafx/downloads/index.html
So let’s move to the code. This application is made up of two core classes Main.java which contains the GUI and registers the form action handlers and MemberController.java which executes the REST Calls using RESTEasy client API and modifies the model accordingly.
Here’s Main.java
public class Main extends Application implements EventHandler<ActionEvent>, ServerResponseHandler { private TextField txtName; private TextField txtEmail; private TextField txtPhone; // Labels with the error messages private Label lblMessageName; private Label lblMessageEmail; private Label lblMessagePhoneNumber; private Messages messages; private TableView<Member> tableView; private Pane mainPane; private TitledPane errorPane; private ProgressIndicator progressIndicator; private final String MAIN_CSS = getClass().getResource( "/resources/css/main.css").toString(); MemberController controller = new MemberController(); public static void main(String[] args) { launch(args); } @Override public void start(Stage palco) throws Exception { List<String> parameters = getParameters().getUnnamed(); if (parameters.size() > 0) { String url = parameters.get(0); System.out.println(url); } StackPane root = new StackPane(); root.getChildren().addAll(createMainPane(), createProgressIndicator(), createErrorPane()); palco.setScene(SceneBuilder.create().width(500).height(400).root(root) .stylesheets(MAIN_CSS).build()); palco.setTitle("KitchensinkFX"); palco.setResizable(false); palco.show(); updateData(); } private Pane createMainPane() { return mainPane = VBoxBuilder.create() .children(createNewMemberForm(), createListMembersPane()) .build(); } private ProgressIndicator createProgressIndicator() { progressIndicator = ProgressIndicatorBuilder.create().maxWidth(100) .visible(false).maxHeight(100).build(); progressIndicator.visibleProperty().bindBidirectional( mainPane.disableProperty()); return progressIndicator; } private TitledPane createErrorPane() { errorPane = TitledPaneBuilder .create() .text("Error on server communication!") .visible(false) .prefHeight(150) .maxWidth(250) .content( LabelBuilder .create() .wrapText(true) .maxWidth(200) .styleClass("error-label", "panel-error-label ") .build()).collapsible(false).build(); errorPane.setOnMouseClicked(new EventHandler<Event>() { public void handle(Event arg0) { errorPane.setVisible(false); } }); mainPane.visibleProperty().bind(errorPane.visibleProperty().not()); return errorPane; } private Node createNewMemberForm() { GridPane newMemberForm = GridPaneBuilder.create().vgap(10).hgap(5) .translateX(10).translateY(10).prefHeight(300).build(); newMemberForm.add(new Label("Name"), 0, 0); newMemberForm.add(txtName = new TextField(), 1, 0); newMemberForm.add( lblMessageName = LabelBuilder.create() .styleClass("error-label").maxWidth(250).build(), 2, 0); newMemberForm.add(new Label("Email"), 0, 1); newMemberForm.add(txtEmail = new TextField(), 1, 1); newMemberForm.add( lblMessageEmail = LabelBuilder.create() .styleClass("error-label").maxWidth(250).build(), 2, 1); newMemberForm.add(new Label("Phone"), 0, 2); newMemberForm.add(txtPhone = new TextField(), 1, 2); newMemberForm.add(lblMessagePhoneNumber = LabelBuilder.create() .styleClass("error-label").maxWidth(250).build(), 2, 2); newMemberForm.add( ButtonBuilder.create().text("Add Member").onAction(this) .build(), 1, 3); return newMemberForm; } @SuppressWarnings({ "unchecked", "rawtypes" }) private Node createListMembersPane() { tableView = new TableView<Member>(); TableColumn emailColumn = new TableColumn<Member, String>(); emailColumn.setCellValueFactory(new PropertyValueFactory("email")); TableColumn nameColumn = new TableColumn(); nameColumn.setCellValueFactory(new PropertyValueFactory("name")); TableColumn phoneColumn = new TableColumn(); phoneColumn .setCellValueFactory(new PropertyValueFactory("phoneNumber")); emailColumn.setText("Email"); nameColumn.setText("Name"); phoneColumn.setText("Phone Number"); phoneColumn.setPrefWidth(150); tableView.getColumns().setAll(nameColumn, emailColumn, phoneColumn); return tableView; } private void updateData() { progressIndicator.setVisible(true); controller.getAllMembers(this); } private void clearMessagesAndFields() { lblMessageEmail.setText(""); lblMessageName.setText(""); lblMessagePhoneNumber.setText(""); txtEmail.setText(""); txtName.setText(""); txtPhone.setText(""); } private void showMessages() { lblMessageEmail.setText(messages.getEmail()); lblMessageName.setText(messages.getName()); lblMessagePhoneNumber.setText(messages.getPhoneNumber()); } private void showErrorMessage(String errorMessage) { ((Label) errorPane.getContent()).setText(errorMessage); errorPane.setVisible(true); } public void onMembersRetrieve(final List<Member> members) { Platform.runLater(new Runnable() { public void run() { tableView.setItems(FXCollections.observableList(members)); progressIndicator.setVisible(false); } }); } public void onMemberCreation(final Messages serverMessages) { Platform.runLater(new Runnable() { public void run() { messages = serverMessages; if (serverMessages == null) { clearMessagesAndFields(); updateData(); } else { showMessages(); } progressIndicator.setVisible(false); } }); } public void onServerError(final Exception e) { Platform.runLater(new Runnable() { public void run() { showErrorMessage(e.getMessage()); e.printStackTrace(); progressIndicator.setVisible(false); } }); } public void handle(ActionEvent evt) { progressIndicator.setVisible(true); controller.createMember( new Member(txtName.getText(), txtEmail.getText(), txtPhone .getText()), this); } }
We will not enter into the details of GUI creation; we will just draw your attention on the handle method, which is the one that is invoked when you click on the Register button and invokes the Controller’s create method passing the text fields and a reference to the ServerResponseHandler interface that is implemented by the class:
The ServerResponseHandler is used to handle the server responses when you want to make a non thread locking request
public interface ServerResponseHandler { public void onMembersRetrieve(List<Member> members); public void onMemberCreation(Messages messages); public void onServerError(Exception e); }
Then the MemberController class is responsible to invoke the Kitchensink REST members service (available at http://localhost:8080/jboss-as-kitchensink/rest/members ) which returns the list of registered members using JSON.
public class MemberController { /** * The Base Url for the service */ private String applicationUrl = "http://localhost:8080/jboss-as-kitchensink/rest/members"; /** * * It simply makes an HTTP request to create a Member * * @param member */ @SuppressWarnings("unchecked") public Messages createMember(Member member) { ClientRequest cr = new ClientRequest(applicationUrl); cr.body(MediaType.APPLICATION_JSON, member); try { return (Messages) cr.post().getEntity(Messages.class); } catch (Exception e) { throw new RuntimeException(e); } } public void createMember(final Member member, final ServerResponseHandler serverResponseHandler) { Thread t = new Thread(new Runnable() { public void run() { try { serverResponseHandler .onMemberCreation(createMember(member)); } catch (Exception e) { serverResponseHandler.onServerError(e); } } }); t.start(); } @SuppressWarnings("unchecked") public List<Member> getAllMembers() throws Exception { ClientRequest cr = new ClientRequest(applicationUrl); cr.accept(MediaType.APPLICATION_JSON); return (List<Member>) cr.get().getEntity( new GenericType<List<Member>>() { }); } public void getAllMembers(final ServerResponseHandler serverResponseHandler) { Thread t = new Thread(new Runnable() { public void run() { try { serverResponseHandler.onMembersRetrieve(getAllMembers()); } catch (Exception e) { serverResponseHandler.onServerError(e); } } }); t.start(); } }
As you can see, the actual invocation is performed into the public Messages createMember(Member member), which uses the org.jboss.resteasy.client.ClientRequest class to perform an HTTP Request. This method returns a Message object which is barely a wrapper for the Member we are trying to register.
Running the Java FX application
The full project is hosted at this fork of JBoss developer framework: https://github.com/fmarchioni/jboss-as-quickstart/tree/master/kitchensink-javafx
Before running the maven project, you have to check for the correct javafx-path, which is in the pom.xml
<javafx-path>C:\Java\jdk1.7.0_09\jre\lib\jfxrt.jar</javafx-path>
Launch the main application using
mvn install -Prun
Which will invoke the “run” profile that in turn will run the project’s Main class.
Here’s how the main form looks like:
Notice how validation error messages are beautifully handled in the Main class’ errorPane:
If you pass all validation checks, the new Member is finally added into JavaFX javafx.scene.control.TableColumn, just like it does in the KitchenSink dataTable component:
Enjoy JavaFX and RESTeasy services !