How to write a simple client-server calculator (JavaFX + EJB + WildFly)

So, let's say we want to implement a client-server application where the necessary data will be generated on the client side, and on the server side, the calculation will be made and returned to the client as a result. If we take into account a simple calculator (let's make it even easier, 4 operators, operands without fractions and work according to the scheme [operand1] [operator] [operand2] [result]) and, let's say, implement it in some kind of programming language (programming language ), for example, Java, using the application server (for example, WildFly / JBoss) + client (you can use JavaFX), you can do this in the following way:



The same option can be solved using RMI (Remote Method Invocation) without a server, GUI client, and EJB in the console, but we will not consider this option, but proceed to a more interesting implementation.



1. We will need the following ingredients:

1.1. JDK,

1.2. IDE (with Java EE support),

1.3. WildFly (or another Java application server),

1.4. SceneBuilder (for convenience and quick GUI creation).



To connect the client to the server, we will use JNDI (a naming and directory service) using EJB (a framework for building business logic).



2. Create an implementation of the server side:

2.1. The remote interface through which communication between clients and the server will take place (we use the Remote annotation - the EJB component will use RMI).



package com.calc.server; import javax.ejb.Remote; @Remote public interface CalcRemote { int add(int a, int b); int sub(int a, int b); int mul(int a, int b); int div(int a, int b) throws MyException; }
      
      





2.2. A class that implements this interface (we use the annotation @Stateless - a stateless session component).



 package com.calc.server; import javax.ejb.Stateless; @Stateless(name = "CalcSessionEJB") public class CalcSessionBean implements CalcRemote { public CalcSessionBean() { } @Override public int add(int a, int b) { return a + b; } @Override public int sub(int a, int b) { return a - b; } @Override public int mul(int a, int b) { return a * b; } @Override public int div(int a, int b) throws MyException { try { return a / b; } catch (ArithmeticException ex) { throw new MyException("Divide by Zero!!!"); } } }
      
      





2.3. Add our exception class, which will signal division by zero or the wrong format.



 package com.calc.server; public class MyException extends Exception { public MyException(String message) { super(message); } }
      
      





2.4. Using the IDE, we create an ear-file (Enterprise Archive), start the server (it is possible with a standard port), deploy to it, and if no errors were noticed, then the server part is finished.



The server log can be like this:



  java:global/Calc_ear_exploded/ejb/CalcSessionEJB!com.calc.server.CalcRemote java:app/ejb/CalcSessionEJB!com.calc.server.CalcRemote java:module/CalcSessionEJB!com.calc.server.CalcRemote java:jboss/exported/Calc_ear_exploded/ejb/CalcSessionEJB!com.calc.server.CalcRemote ejb:Calc_ear_exploded/ejb/CalcSessionEJB!com.calc.server.CalcRemote java:global/Calc_ear_exploded/ejb/CalcSessionEJB java:app/ejb/CalcSessionEJB java:module/CalcSessionEJB
      
      





3. We create the implementation of the client part:

3.1. In SceneBuilder we sketch the following calculator layout (main.fxml), we will not wind up css yet:



 <?xml version="1.0" encoding="UTF-8"?> <?import javafx.scene.control.*?> <?import javafx.scene.layout.*?> <AnchorPane id="AnchorPane" maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" prefHeight="321.0" prefWidth="231.0" xmlns:fx="http://javafx.com/fxml/1" xmlns="http://javafx.com/javafx/2.2" fx:controller="com.calc.client.impl2.controller.Controller"> <children> <Label disable="false" layoutX="24.0" layoutY="15.0" prefHeight="28.0" prefWidth="189.0" text="EJB Calculator" /> <TextField fx:id="displayTextField" layoutX="24.0" layoutY="43.0" prefHeight="56.0" prefWidth="182.0" /> <Button fx:id="num7Button" layoutX="24.0" layoutY="110.0" mnemonicParsing="false" prefHeight="39.0" prefWidth="40.0" text="7" /> <Button fx:id="num8Button" layoutX="72.0" layoutY="110.0" mnemonicParsing="false" prefHeight="39.0" prefWidth="40.0" text="8" /> <Button fx:id="num9Button" layoutX="119.0" layoutY="110.0" mnemonicParsing="false" prefHeight="39.0" prefWidth="40.0" text="9" /> <Button fx:id="divButton" layoutX="166.0" layoutY="110.0" mnemonicParsing="false" prefHeight="39.0" prefWidth="40.0" text="/" /> <Button fx:id="num4Button" layoutX="24.0" layoutY="161.0" mnemonicParsing="false" prefHeight="39.0" prefWidth="40.0" text="4" /> <Button fx:id="num5Button" layoutX="72.0" layoutY="161.0" mnemonicParsing="false" prefHeight="39.0" prefWidth="40.0" text="5" /> <Button fx:id="num6Button" layoutX="119.0" layoutY="161.0" mnemonicParsing="false" prefHeight="39.0" prefWidth="40.0" text="6" /> <Button fx:id="mulButton" layoutX="166.0" layoutY="161.0" mnemonicParsing="false" prefHeight="39.0" prefWidth="40.0" text="*" /> <Button fx:id="num1Button" layoutX="24.0" layoutY="209.0" mnemonicParsing="false" prefHeight="39.0" prefWidth="40.0" text="1" /> <Button fx:id="num2Button" layoutX="72.0" layoutY="209.0" mnemonicParsing="false" prefHeight="39.0" prefWidth="40.0" text="2" /> <Button fx:id="num3Button" layoutX="119.0" layoutY="209.0" mnemonicParsing="false" prefHeight="39.0" prefWidth="40.0" text="3" /> <Button fx:id="subButton" layoutX="166.0" layoutY="209.0" mnemonicParsing="false" prefHeight="39.0" prefWidth="40.0" text="-" /> <Button fx:id="num0Button" layoutX="24.0" layoutY="259.0" mnemonicParsing="false" prefHeight="39.0" prefWidth="40.0" text="0" /> <Button fx:id="clrButton" layoutX="72.0" layoutY="259.0" mnemonicParsing="false" prefHeight="39.0" prefWidth="40.0" text="C" /> <Button fx:id="resButton" layoutX="119.0" layoutY="259.0" mnemonicParsing="false" prefHeight="39.0" prefWidth="40.0" text="=" /> <Button fx:id="addButton" layoutX="166.0" layoutY="259.0" mnemonicParsing="false" prefHeight="39.0" prefWidth="40.0" text="+" /> </children> </AnchorPane>
      
      





3.2. We connect the class controller to the fxml form, to unlock the GUI (sticky form) while waiting for data from the server, add a new thread to the "=" button:



Code view
 package com.calc.client.impl2.controller; import com.calc.server.CalcRemote; import com.calc.server.MyException; import javafx.fxml.FXML; import javafx.scene.control.Button; import javafx.scene.control.TextField; import javax.naming.Context; import javax.naming.InitialContext; import javax.naming.NamingException; import java.util.Properties; public class Controller implements Runnable { private Calculator calculator; @FXML private TextField displayTextField; @FXML private Button num1Button; @FXML private Button num2Button; @FXML private Button num3Button; @FXML private Button num4Button; @FXML private Button num5Button; @FXML private Button num6Button; @FXML private Button num7Button; @FXML private Button num8Button; @FXML private Button num9Button; @FXML private Button num0Button; @FXML private Button addButton; @FXML private Button subButton; @FXML private Button mulButton; @FXML private Button divButton; @FXML private Button clrButton; @FXML private Button resButton; @FXML private void initialize() { System.out.println("initialize()"); calculator = new Calculator(); displayTextField.setText("0"); num1Button.setOnAction(event -> displayTextField.setText(calculator.addNumber("1"))); num2Button.setOnAction(event -> displayTextField.setText(calculator.addNumber("2"))); num3Button.setOnAction(event -> displayTextField.setText(calculator.addNumber("3"))); num4Button.setOnAction(event -> displayTextField.setText(calculator.addNumber("4"))); num5Button.setOnAction(event -> displayTextField.setText(calculator.addNumber("5"))); num6Button.setOnAction(event -> displayTextField.setText(calculator.addNumber("6"))); num7Button.setOnAction(event -> displayTextField.setText(calculator.addNumber("7"))); num8Button.setOnAction(event -> displayTextField.setText(calculator.addNumber("8"))); num9Button.setOnAction(event -> displayTextField.setText(calculator.addNumber("9"))); num0Button.setOnAction(event -> displayTextField.setText(calculator.addNumber("0"))); addButton.setOnAction(event -> { calculator.addOperator("+"); displayTextField.setText(""); }); subButton.setOnAction(event -> { calculator.addOperator("-"); displayTextField.setText(""); }); mulButton.setOnAction(event -> { calculator.addOperator("*"); displayTextField.setText(""); }); divButton.setOnAction(event -> { calculator.addOperator("/"); displayTextField.setText(""); }); resButton.setOnAction(event -> new Thread(this).start()); clrButton.setOnAction(event -> displayTextField.setText("")); } private void doRequest(String[] data) throws NamingException, MyException { Properties props = new Properties(); props.put(Context.INITIAL_CONTEXT_FACTORY, "org.wildfly.naming.client.WildFlyInitialContextFactory"); // props.put(Context.URL_PKG_PREFIXES, "org.jboss.ejb.client.naming"); // props.put(Context.PROVIDER_URL, "remote+http://localhost:8080"); props.put(Context.PROVIDER_URL, "http-remoting://localhost:8080"); // props.put("jboss.naming.client.ejb.context", "true"); // props.put(Context.SECURITY_PRINCIPAL, "admin"); // props.put(Context.SECURITY_CREDENTIALS, "123"); Context ctx = new InitialContext(props); //System.out.println(ctx.getEnvironment()); CalcRemote calcRemote = (CalcRemote) ctx.lookup("Calc_ear_exploded/ejb/CalcSessionEJB!com.calc.server.CalcRemote"); //java:jboss/exported/Calc_ear_exploded/ejb/CalcSessionEJB!com.calc.server.CalcRemote String res = Integer.toString(process(calcRemote, data)); displayTextField.setText(res); System.out.println(res); } private int process(CalcRemote calcRemote, String[] resData) throws MyException { int operand1 = Integer.parseInt(resData[0]); int operand2 = Integer.parseInt(resData[1]); String operator = resData[2]; switch (operator) { case "+": return calcRemote.add(operand1, operand2); case "-": return calcRemote.sub(operand1, operand2); case "*": return calcRemote.mul(operand1, operand2); case "/": return calcRemote.div(operand1, operand2); } return 0; } @Override public void run() { displayTextField.setText("WAITING..."); try { doRequest(calculator.getResult()); } catch (NamingException ex) { ex.printStackTrace(); } catch (MyException ex) { displayTextField.setText(ex.getMessage()); } } }
      
      









3.3. Add the logic of the simplest calculator:



 package com.calc.client.impl2.controller; import com.calc.server.MyException; public class Calculator { private String buffer = "", operator, operand1, operand2; private boolean isOperator = false; public String addNumber(String value) { buffer += value; if (!isOperator) { operand1 = buffer; } else { operand2 = buffer; } return buffer; } public void addOperator(String value) { operator = value; buffer = ""; isOperator = true; } public String[] getResult() throws MyException { isOperator = false; buffer = ""; int check; try { check = Integer.parseInt(operand1); check = Integer.parseInt(operand2); } catch (NumberFormatException ex) { throw new MyException("Wrong format!!!"); } return new String[]{operand1, operand2, operator}; } }
      
      





3.5. Add the client library (in the case of WildFly it is jboss-client.jar), run the GUI.



As can be seen from the attached codes, the user types digits in the buffer for operands, selects the operator, when the "=" button is pressed, a connection is made to the server (via the JNDI service), the inscription on the display "WAITING ..." will indicate that the server is waiting for a response.



We also note a couple of exceptions: NumberFormatException (caught when the user enters data on the client) and ArithmeticException (caught when divided by zero on the server).



All Articles