Java™ - Best Practice for Remote Communication with Java RMI

in #utopian-io7 years ago (edited)

Repository

https://github.com/JetBrains/intellij-community

What Will I Learn?

  • You will learn about Java™ RMI API
  • You will learn about JavaFX
  • You will learn about Java™ Generics (Abstract types)
  • You will learn how to simultaneously perform multiple tasks (concurrent programming) and synchronization
  • You will learn how to invoke a method regardless of its declaration (Reflection) during the runtime
  • You will learn how to create the service more flexible by always providing connection to the database during the runtime

Requirements

And this is my current system states:
Untitled 1.png

Difficulty

  • Advanced

Tutorial Contents


If you are looking for the same related tutorial about Java RMI, there are so many articles, and you will get stuck and can not even see the programs you have created based on those tutorials running properly. In fact, Java RMI is very easy, but I will make this tutorial more powerful and flexible. So, later on it will be more easily extended and maintainable.

Normally, you as a developer will use standard protocols (most commonly: HTTP, SMTP, FTP or SOAP) to exchange data between systems or machines with others. The main thing that underlies this problem is the compatibility.
For example, a browser needs data from a machine which runs Java programs. When the data has been received by the browser, it does not understand the Java byte-code or object (instance of the class) in the context of Java, because it understands JavaScript only. Thus, they agree on the exchange of data that can be understood by each of them, i.e. text, but by following certain rules to distinguish them from the plain text. So as of now, we already know XML, JSON or something else, and the protocol is HTTP/HTTPS.

Fortunately, we can exchange data between two separate machines without having to follow the existing standard rules, using Remote Method Invocation (RMI) in the term of Java programming language or better known in other programming languages as Remote Procedure Call (RPC), as simple as regular method invocations. And even more sophisticated, we can handle exceptions over the network.
Of course, RMI can be applied if the machines communicate with each other installed the Java Virtual Machine.

RMI/RPC is commonly applied to distributed systems

Overview

There will be two systems that will communicate with each other. They are Timeless, as the server that authenticates users through the given parameters of the username and password, so it provides a remote object that has a signin method which then can be called by other systems remotely.
And ChitChat, which only focus as user interface and will send the username and password to the remote object provided by the server. But not only that, it will also send a callback reference (as the name bound to the remote object). So once the server has finished authenticating the user in a session, the server will invoke the callback method and send the response through its parameters.
The advantage of using this concept is, it does not necessarily have to create a separate thread to wait for an unpredictable authentication process.

The whole series of authentication is summarized in the following figure,

RMI Authentication.png

User Authentication via Java™ RMI

  1. Database Setup
  2. Build up Timeless Project — As the server
    1. Create DataPackage — As entity class to store data
    2. Create Authenticable — Interface in which the instance can be a remote object for authentication
    3. Create Executor — Abstract thread can perform any task in general
    4. Create SQLMaintainable — Interface to get maintainable database connection
    5. Create Processor — As the implementation of Executor for authentication
    6. Create RemoteAuthenticator — As the implementation of Authenticable
    7. Create Main — As the launcher of this system and manager of client requests
  3. Build up ChitChat Project — As the client.
    1. Create EventListenable — Interface in which the instance can be a remote object for callback reference
    2. Create SigninPane — As the provider of Signin layout
    3. Create SigninPane.EventImplementer — As the implementation of EventListenable
    4. Create Main — As launcher of this system
  4. Deployment and Running up

1. Database Setup


  • Login as root and enter your password
    mysql -u root -p

  • Create database Murez
    CREATE DATABASE Murez

  • Create table of User
    CREATE TABLE `User`(
    `Email` VARCHAR(128) PRIMARY KEY NOT NULL,
    `Password` VARCHAR(256) NOT NULL,
    `Name` VARCHAR(256) NOT NULL,
    `Gender` ENUM('1', '0') NOT NULL,
    `Birthday` DATE NOT NULL,
    `Status` TEXT)

  • Insert one record for testing
    INSERT INTO `User` VALUE("[email protected]",SHA1("myPass987"),"Murez Nasution","1","1989-09-29",NULL)

  • Create a new database user as Timeless for login
    CREATE User 'Timeless'@'localhost' IDENTIFIED BY 'myPass123'

  • Grant access to user of Timeless
    GRANT ALL ON Murez.* TO 'Tester'@'localhost'

Now the Timeless app has gained access to the database by using:
Username: "Timeless", and Password: "myPass123".
And also we can sign in to the system via ChitChat app by using:
Username: "[email protected]", and Password: "myPass987".

More details for database setup can be learned here on section (1) Settings up Database


2. Build up Timeless Project


  • Open your IntelliJ IDEA.
  • On the Welcome to IntelliJ IDEA dialog, select Create New Project.
    Then will appear New Project dialog.
  • On the left panel, select Java and then check Kotlin/JVM. Click Next.
  • Change your Project location to D:\Murez\Home\Project\Java\, and then type "Timeless" on the Project name.
  • Finally click Finish.

And here is the project structure that we will create,

Untitled 6.png



2.1. Create DataPackage


Actually, we can use the key-value pairs in which all objects that implement java.util.Map interface as alternative data structure which is flexible for various conditions, but here we need to read the simple one directly without having to search to the pairs. Thus, we provide two variables as options that can store numbers or string in addition to the PACKAGE (pairs of the keys map to the values).

Furthermore, we want to give flexibility to the next developers to decide for themselves the suitable type of the values mapped to the keys. In this case we can assume them as T, which T is an abstract type (Generic) that can be substituted by various types (actually classes). A class whose members have an abstract type (especially field, since the method can have its own generic) must declare it as a parameter, as in the following figure,

Untitled 2.png

If we pass the value of String, then we will get the value of getPackage as the pairs of the keys map to the value of String as well.

Map<String, String> pairs = new DataPackage<String>(0, "", new HashMap<>()).getPackage();

or,

Map<String, String> pairs = new DataPackage<>(0, "", new HashMap<String, String>()).getPackage();

For more flexible functionality, we will also provide a static method that will returns the instance of DataPackage<String> with the default implementation of the key-value pairs as java.util.HashMap.
And the following is the source codes of DataPackage class,

package com.murez.entity;

import java.util.Map;

public class DataPackage<T> implements java.io.Serializable {
    private final Number N;
    private final String S;
    private final Map<String, T> PACKAGE;

    public DataPackage(Number n, String s, Map<String, T> instance) {
        N = n;
        S = s;
        if((PACKAGE = instance) == null)
            throw new IllegalArgumentException();
    }

    public static DataPackage<String> create(Number n, String s) {
        return new DataPackage<>(n, s, new java.util.HashMap<>());
    }

    public final Number getNumber(Number defaultValue) { return N == null? defaultValue : N; }

    public final String getString(String defaultValue) { return S != null && S.length() > 0? S: defaultValue; }

    public Map<String, T> getPackage() { return PACKAGE; }
}



2.2. Create Authenticable


As explained earlier, on the server side we need to provide a remote object that has a signin method to authenticate the users.
In Java, all remote objects must be implementations of the java.rmi.Remote interface or their sub-classes. And the method that should be visible to the client to be invoked remotely must be declared by throwing java.rmi.RemoteException exception. As simple as the following,

package com.murez.remote;

import com.murez.entity.DataPackage;

public interface Authenticable extends java.rmi.Remote {
    void signin(String username, String password, DataPackage<String> dataPack) throws java.rmi.RemoteException;
}


For the methods which can be invoked remotely, then:

  1. Must be declared by throwing java.rmi.RemoteException exception
  2. As a member of the class that implements java.rmi.Remote or its sub-classes.


2.3. Create Executor


The most important stuff that must be managed properly by the server is the thread. There are so many clients who will access some limited resources. If we do not have some tricks to maintain available resources, they will become inconsistent. The best solution to solve this case is to use sync. It is about serializing the parallel tasks, so there will be no thread that will try to precede the other threads as long as they have not completed their task.

We need a thread that will always wait to perform a certain process as long as the thread is not notified. The notifiers can not be accessed in the common way, because some resources must be initialized first and we need to keep them consistent. So, we provide a method that will returns null if the current thread is still active, or some resumable object if it is waiting for and through that object we notify it to be active, of course after initializing the necessary resources which are needed during the process.
Untitled 3.png
Untitled 4.png

Therefore, we designed an abstract class Executor as special thread that starts immediately and already manages the states well and the next developers only need to implement the callbacks which are already available,

  • T onActive() — The core process and must returns the abstract type of the result.
  • void onFinish(T) — This method is in the synchronized block which will receive the result as parameter and just deliver it.
  • void onClose() — This method will be called if the currently waiting thread is interrupted.

Untitled 5.png

And the following is the source codes of Executor<T> class,

package com.murez.util;

public abstract class Executor<T> implements Runnable {
    private final Object LOCK = new Object();
    private final Resumable R;
    private Boolean active;
    private boolean resist;

    protected Executor() {
        R = () -> {
            synchronized(this) {
                synchronized(LOCK) { LOCK.notify(); }
                resist = false;
            }
        };
        new Thread(this).start();
    }

    @Override
    public final void run() {
        for(T out = null; ; out = onActive())
            synchronized(LOCK) {
                if(active != null)
                    onFinish(out);
                active = false;
                try { LOCK.wait(); }
                catch(InterruptedException e) {
                    onClose();
                    break;
                }
                active = !active;
            }
    }

    protected Resumable acquires() {
        synchronized(R) {
            if(!resist) {
                boolean deactived;
                synchronized(LOCK) {
                    deactived = active != null && !active;
                }
                if(deactived) {
                    resist = !resist;
                    return R;
                }
            }
            return null;
        }
    }

    protected abstract T onActive();

    protected abstract void onFinish(T response);

    protected abstract void onClose();

    public interface Resumable {
        void resume();
    }
}



2.4. Create SQLMaintainable


We need to keep connections to the database alive as long as the app runs. If they're lost, the app will attempt to regain them. So, we provide an interface to be able to get database connections bound to properties file. Because it is possible to change the source from database A to B during the runtime. Instead we have to re-code to replace the database parameters which causes the app to be recompiled and then restarted, we can make them as the properties file. So, we will simply replace the database parameters through the properties file without having to change the source code and the app keeps on alive.

And the following is the source codes of SQLMaintainable interface

package com.murez.sql;

public interface SQLMaintainable {
    java.sql.Connection getConnection() throws java.io.IOException;
}



2.5. Create Processor


The class of Processor is the sub-class of Executor<T> abstract class. It will perform the main process to authenticate the users. Each user will be handled by a processor. If an authentication has been completed, then it's ready to handle the others. It will implement three abstract methods, onActive(), onFinish(response) and onClose() inherited by its super class, Executor<T>. And here's the explanation.

onActive()
Untitled 7.png
Untitled 8.png

onFinish(response)
Untitled 9.png

Whenever the server has finished authenticating the users, it must return the result by calling a particular method through a remote object. As we know that the lookup(name) method will return the remote object as the java.rmi.Remote instance and we never know what class to be used in order to cast and then invoke the callback method correctly. We do not want the server to adjust to the various callbacks supplied by different clients. Therefore, we can use Java™ Reflection to solve this problem.

We expect clients to provide callbacks with the following signatures:
void onFinish(DataPackage dataPack).
So, the steps you should do are:

  1. Get the class definition of the related remote object by executing: remote.getClass().
  2. Search the related method by its name "onFinish" and list of parameter types which the same as DataPackage.class.
  3. Finally, invoke the target method. By passing the real instance of remote object and the instance of its parameter.

Thus the server is only concerned with the host address, port number, the name bound to the remote object and the callback name provided by the clients. And the client can replace its callback references repeatedly and let the server stay alive.

onClose()

@Override
public void onClose() {
    try {
        if(!connector.isClosed())
            connector.close();
    } catch(Exception e) { connector = null; }
}

Close the database connection at any time the processor will be terminated. Each processor holds one connection, so leaving the connection still active while the processor is no longer used will lead to a resource leak.

Notify the Processor

public void resume(String username, String password, DataPackage<String> dataPack) {
    Executor.Resumable notifier = acquires();
    if(notifier != null) {
        if(!TEXT.apply(username))
            throw new IllegalArgumentException("Username shouldn't be empty");
        else this.username = username;
        if(!TEXT.apply(password))
            throw new IllegalArgumentException("Password shouldn't be empty");
        else this.password = password;
        String[] keys = { "name" };
        if(dataPack == null
            || dataPack.getNumber(null) == null
            || dataPack.getString(null) == null
            || !dataPack.getPackage().keySet().containsAll(Arrays.asList(keys)))
            throw new IllegalArgumentException("Properties of client's remote callback should be defined correctly");
        else this.dataPack = dataPack;
        notifier.resume();
    } else
    throw new UnsupportedOperationException("This processor was unavailable");
}

As described recently, we must first obtain a resumable object before it can notify the processor to be active. We can get this object by calling the inheritance method acquires. Always check the returned objects, if not null, then we can notify by first completely validating and initializing all of the resources to be used during the process.

Because the source code is too long, then I attach it here, Processor.java.


2.6. Create RemoteAuthenticator


The class is the implementation of Authenticatable interface which means that the instance of this one will be exportable into a remote object. It's quietly so simple. When the user sign in, then the remote method will try to fetch the processors in the queue. For three seconds, if the processor is still not available then the server will send the "Service Unavailable" in response. If it's available, the remote object will pass the username, password and data as the arguments of resume method which after validating and initializing them will notify the processor. But, if the validation fails, the authentication can not continue and then the server sends the "Bad Request" in response.

package com.murez.net;

import com.murez.util.Processor;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.TimeUnit;

public class RemoteAuthenticator implements com.murez.remote.Authenticable {
    private final BlockingQueue<Processor> PROCESSORS;

    public RemoteAuthenticator(BlockingQueue<Processor> processors) {
        PROCESSORS = processors;
    }

    @Override
    public void signin(String username, String password, com.murez.entity.DataPackage<String> dataPack) {
        Processor instance;
        try {
            instance = PROCESSORS.poll(3, TimeUnit.SECONDS);
        } catch(InterruptedException e) {
            instance = null;
        }
        if(instance == null)
            throw new UnsupportedOperationException("503 Service Unavailable");
        else
            try { instance.resume(username, password, dataPack); }
            catch(IllegalArgumentException e) {
                e.printStackTrace();
                throw new IllegalArgumentException("400 Bad Request", e);
            }
            catch(Exception e) {
                throw new UnsupportedOperationException("500 Internal Server Error", e);
            }
    }
}



2.7. Create Main


And finally, it's time to set up the main method to run the Timeless as a server. Before you can start it properly, first you should prepare the following set of dependencies:

  1. Create the properties file. If you notice it also as the supplier of the maintainable database connection, because it implements SQLMaintainable interface. So, you have to write into the file using the Text Editor the three pairs of keys and their values depending on the database that will be used by the app. Based on the section (1) Database Setup we have done before, then you should write as follows:
    database=Murez
    username=Timeless
    password=myPass123

    and finally save as timeless.properties in the directory: D:\Murez\Home\Project\Java\Timeless. Alternatively, you can customize the name and/or path by passing the new one to the app arguments. But, you can't trick if the new path has space separator.
    Untitled 11.png
    Then if at any time you want to change the source of database for maintenance, do not necessarily have to change the parameters through the source code, but simply replace it from the properties file and let the server stay alive.

  2. Install the Security Manager and create the policy file.
    Untitled 12.png
    We will install a security manager in this app, so it will restrict the tasks to perform any operations that require permissions and only to the trusted resources. Then we will grant all the permissions to the local class path of Timeless's app and also JAR of MySQL Connector as the app dependency we will import later in the next point.
    Open your Text editor and write down these codes:

    grant codeBase "file:/D:/Murez/Home/Project/Java/Timeless/out/production/Timeless" {
        permission java.security.AllPermission;
    };
    grant codeBase "file:/C:/Program Files (x86)/MySQL/Connector J 8.0/mysql-connector-java-8.0.11.jar" {
        permission java.security.AllPermission;
    };
    

    and save as server.policy in the same directory: D:\Murez\Home\Project\Java\Timeless.

  3. Download MySQL Connector/J, extract the JAR file and then import it into your project in the following way:

    1. Click FileProject Structure... or simply press Ctrl + Alt + Shift + S, and then new dialog window will appear.
    2. Click + (plus sign) at the most right ⇢ JARs or directories.
    3. Browse to your mysql-connector-java-XXX.jar file.
      If you've installed Connector/J via the MySQL Installer, you can simply find it at C:\Program Files (x86)\MySQL\Connector J 8.0\. Alternatively you can download it here.
    4. Finally click OK.
      Untitled 10.png

  4. Prepare the Processors, export a remote object and register the Stub.
    Untitled 13.png

Again the source code is too long, you can look at here.

Finally, we're done in order building the Timeless app. Before step forward to build the next project, we have to export the Authenticable and DataPackage classes as a JAR file. Third parties will never be able to compile their projects without them. And here are the steps,

  • Open your Command Prompt.
  • IntelliJ IDEA set its default output directory at out/production/[Project-Name]. So, change your working directory to D:\Murez\Home\Project\Java\Timeless\out\production\Timeless.
  • Type as follows, jar cvf auth.jar com/murez/remote/Authenticable.class com/murez/entity/DataPackage.class. Then auth.jar file will appear in the current directory.
    Send this file to the ChitChat developers, and we can start to the next development.
    Untitled 14.png


    3. Create EventListenable

Setup a new project as the same as at the section (2), but change its name to ChitChat and the structure will look like the following,

Untitled 15.png



3.1. Create EventListenable


Based on the previous deal that we can define our own remote object as a callback reference, but the server only accepts there must be one method named onFinish and the argument is a response with the DataPackage type.

package com.julisa.remote;

public interface EventListenable extends java.rmi.Remote {
    void onFinish(com.murez.entity.DataPackage<String> response) throws java.rmi.RemoteException;
}



3.2. Create SigninPane


This layout is so simple, we just need two text fields for username and password, a button for submit this form, a label as notifier and progress indicator to show the user that this app is still in authentication. There's no special one in this code, just finish it as simple as create an instance, customize as needed and register every controls according to their container at last. Remember, we just need an instance of this layout per app during its lifecycle, so we can apply this class as singleton.

package com.julisa.ui;

import com.julisa.remote.EventListenable;
import com.murez.entity.DataPackage;
import com.murez.remote.Authenticable;
import javafx.application.Platform;
import javafx.beans.binding.DoubleExpression;
import javafx.event.ActionEvent;
import javafx.event.EventHandler;
import javafx.geometry.Insets;
import javafx.geometry.Pos;
import javafx.scene.control.Button;
import javafx.scene.control.Hyperlink;
import javafx.scene.control.Label;
import javafx.scene.control.PasswordField;
import javafx.scene.control.ProgressIndicator;
import javafx.scene.control.TextField;
import javafx.scene.layout.GridPane;
import javafx.scene.layout.HBox;
import javafx.scene.layout.Pane;
import javafx.scene.layout.VBox;
import java.rmi.registry.LocateRegistry;
import static sample.Main.*;

public class SigninPane {
    private static SigninPane instance;

    public static synchronized Pane getInstance() {
        return (instance == null? instance = new SigninPane() : instance).ROOT;
    }

    public static synchronized EventListenable getListener() {
        return (instance == null? instance = new SigninPane() : instance).listener;
    }

    private final VBox BOX;
    private final Label NOTIFIER;
    private final GridPane ROOT;
    private final Button SUBMIT;
    private final TextField USERNAME, PASSWORD;
    private EventListenable listener;

    private SigninPane() {
        EventImplementer listener = new EventImplementer();
        (USERNAME = new TextField()).setPromptText("Your Email");
            USERNAME.setStyle("-fx-font-size:15px");
            USERNAME.setOnAction(listener);
        (PASSWORD = new PasswordField()).setPromptText("Password");
            PASSWORD.setStyle("-fx-font-size:15px");
            PASSWORD.setOnAction(listener);
        (NOTIFIER = new Label()).setStyle("-fx-text-fill:red");
        (SUBMIT = new Button("Sign in")).setPrefWidth(120);
            SUBMIT.setStyle("-fx-font-size:16px;-fx-padding:5px 20px");
            SUBMIT.setOnAction(listener);
        HBox box = new HBox(20);
            box.setAlignment(Pos.CENTER_RIGHT);
            box.getChildren().addAll(NOTIFIER, SUBMIT);
        Hyperlink register = new Hyperlink("Don't you have an account? Register now");
        //register.setOnAction(e -> ROOT.getScene().setRoot(signupContainer()));
        (BOX = new VBox(10)).setAlignment(Pos.CENTER_RIGHT);
            BOX.getChildren().addAll(box, register);
        Label title = new Label("Welcome to Priceless");
            title.setStyle(STYLE_TITLE);
        (ROOT = new GridPane()).setPadding(new Insets(85, 25, 15, 25));
            ROOT.setAlignment(Pos.TOP_CENTER);
            ROOT.setVgap(10);
            ROOT.setHgap(5);
            ROOT.widthProperty().addListener(observable -> USERNAME.setPrefWidth(((DoubleExpression) observable).get() - 350));
            ROOT.add(title, 0, 0);
            ROOT.add(USERNAME, 0, 1);
            ROOT.add(PASSWORD, 0, 2);
            ROOT.add(BOX, 0, 3);
    }
}



3.3. Create SigninPane.EventImplementer


This class is basically an event listener for submit button, also username and password text fields if the Enter is pressed. During authentication we will show the progress indicator and freeze the text fields and buttons, so that the user does not spam the request until the process has been completed by getting the response from the server, then we reset the controls as early. It should be noted that JavaFX does not allow its controls to be mutated by other threads except its original one. So, we need the Platform.runLater static method to modify our controls.
Untitled 16.png
You'll notice that the EventListenable object is initialized not on the constructor of the class that owns it, but instead in the EventImplementer's inner class. This is because the action on the onFinish method depends on the indicator variable as a member of the EventImplementer, so it will be easier if doing trick like this.

private class EventImplementer implements EventHandler<ActionEvent> {
    private final String NAME = "Authentication";
    private ProgressIndicator indicator;
    private boolean active;

    private EventImplementer() {
        listener = (response) -> {
            final int CODE = response.getNumber(0).intValue();
            if(CODE == 200) {
                if(response.getPackage().get("session") == null) {
                    Platform.runLater(() -> {
                        NOTIFIER.setText(response.getPackage().get("message"));
                        USERNAME.requestFocus();
                    });
                    USERNAME.setEditable(true);
                    USERNAME.setOpacity(1);
                    PASSWORD.setEditable(true);
                    PASSWORD.setOpacity(1);
                    SUBMIT.setDisable(false);
                } else {
                    Platform.runLater(() -> {
                        NOTIFIER.setText("Hello, " + response.getPackage().get("name"));
                        NOTIFIER.setStyle("-fx-text-fill:green");
                        NOTIFIER.requestFocus();
                    });
                }
                USERNAME.setText("");
                PASSWORD.setText("");
            }
            Platform.runLater(() -> BOX.getChildren().remove(indicator));
            synchronized(NAME) {
                if(active) active = false;
            }
        };
    }

    @Override
    public void handle(ActionEvent event) {
        String username, password;
        if((username = TEXT.apply(USERNAME)) == null) return;
        if((password = TEXT.apply(PASSWORD)) == null) return;
        synchronized(NAME) {
            if(!active)
                active = !active;
            else
                return;
        }
        BOX.getChildren().add(indicator = new ProgressIndicator());
        USERNAME.setEditable(false);
        PASSWORD.setEditable(false);
        SUBMIT.setDisable(true);
        USERNAME.setOpacity(.5);
        PASSWORD.setOpacity(.5);
        DataPackage<String> dataPack = DataPackage.create(0, LOCALHOST);
        dataPack.getPackage().put("name", "AuthCallback");
        try {
            final Authenticable REMOTE;
            try {
                REMOTE = (Authenticable) LocateRegistry.getRegistry().lookup(NAME);
            } catch(java.rmi.NotBoundException e) {
                throw new UnsupportedOperationException("There's no remote reference for name \"" + NAME + "\"", e);
            }
            REMOTE.signin(username, password, dataPack);
        } catch(Exception e) { e.printStackTrace(); }
    }
}



3.4. Create Main


Here are the ChitChat's dependencies that have to set before it can run as a client:

  1. Install the Security Manager and create the policy file. Save this code as client.policy in the directory: D:\Murez\Home\Project\Java\ChitChat

    grant codeBase "file:/D:/Murez/Home/Project/Java/ChitChat/out/production/ChitChat" {
        permission java.security.AllPermission;
    };
    

    The statements are not much different from the previous Timeless project.

  2. Import auth.jar file and set the codebase as this app properties. Put this JAR file in the same directory as D:\Murez\Home\Project\Java\ChitChat and the rest do the same import steps as in the previous section.
    The RMI registry should know where the locations/URL it should download the required non-local class paths before being able to accept the stub registration. They are codes that come from the auth.jar file and the EventListenable class. So, we have two references as codebase that must be separated by the spaces depending on the rules. Because there is a space, we can not pass it as an app arguments. So we set it up (hard code) using the System.setProperty method with java.rmi.server.codebase as the key and its value is:
    http://localhost/chitchat/ file:/D:/Murez/Home/Project/Java/ChitChat/auth.jar

  3. Export a remote object and register the Stub. Again still the same as the previous way, but use AuthCallback name and SigninPane.getListener method to get an instance of remote object as the callback reference.

package sample;

import com.julisa.ui.SigninPane;
import javafx.application.Application;
import javafx.beans.binding.DoubleExpression;
import javafx.geometry.Insets;
import javafx.scene.Scene;
import javafx.scene.control.Accordion;
import javafx.scene.control.ListView;
import javafx.scene.control.TextField;
import javafx.scene.control.TextInputControl;
import javafx.scene.layout.GridPane;
import javafx.scene.layout.Pane;
import javafx.stage.Stage;
import java.rmi.registry.LocateRegistry;
import java.rmi.server.UnicastRemoteObject;
import java.util.function.Function;

public class Main extends Application {
    public final static String STYLE_TITLE =
          "-fx-font-size:25px;"
        + "-fx-text-fill:#9a9a9a;"
        + "-fx-font-family:\"Cambria\";"
        + "-fx-font-weight:bold;"
        + "-fx-effect:innershadow(three-pass-box, rgba(0, 0, 0, 0.7), 6, 0.0, 0, 2)";
    public final static String LOCALHOST;
    public final static Function<TextInputControl, String> TEXT = textCtrl -> {
        String text = textCtrl.getText();
        if((text = text != null && text.length() > 0? text : null) == null)
            textCtrl.requestFocus();
        return text;
    };

    static {
        String localhost;
        try {
            localhost = java.net.InetAddress.getLocalHost().getHostAddress();
        } catch(Exception e) {
            e.printStackTrace();
            localhost = null;
        }
        LOCALHOST = localhost;
    }

    public static void main(String[] args) {
        System.setProperty("java.security.policy", "client.policy");
        System.setProperty("java.rmi.server.useCodebaseOnly", "false");
        System.setProperty("java.rmi.server.codebase", "http://localhost/chitchat/ file:/D:/Murez/Home/Project/Java/ChitChat/auth.jar");
        if(System.getSecurityManager() == null)
            System.setSecurityManager(new SecurityManager());
        try {
            LocateRegistry.getRegistry().rebind("AuthCallback", UnicastRemoteObject.exportObject(SigninPane.getListener(), 0));
        } catch(Exception e) {
            e.printStackTrace();
            return;
        }
        launch(args);
    }

    @Override
    public void start(Stage primaryStage) {
        primaryStage.setScene(new Scene(SigninPane.getInstance(), 800, 400));
        primaryStage.setTitle("Priceless");
        primaryStage.setMinHeight(350);
        primaryStage.setMinWidth(650);
        primaryStage.show();
    }
}



At last, we have finished Timeless and ChitChat projects completely.


4. Deployment and Running up

  1. Start RMI Registry by opening Command Prompt and then type this following command,
    rmiregistry -J-Djava.rmi.server.useCodebaseOnly=false
  2. Start the Timeless.
    Focus on the Main.java document, then press Ctrl + Shift + F10 to execute main method. You will get errors, but just ignore it. It happens normally because we have not specified the codebase yet as our JVM option, and the RMI Registry can not find the required classes in its local class path and do not know where to download them. So, we have to provide the path of JAR file containing Authenticable and DataPackage classes as a codebase by performing these following steps:
    • Click RunEdit Configurations..., then Run/Debug Configurations dialog will appear.
    • Click Configuration tab and type on the VM options text field as follows: -Djava.rmi.server.codebase=file:/D:/Murez/Home/Project/Java/Timeless/auth.jar.
      Untitled 17.png
    • Finally, click OK and rerun app by pressing Ctrl + F5.
  3. Start the ChitChat.
    Focus on the Main.java document, but before starting this app, make sure we are already exporting the EventListenable class to the public directory. And here are the steps:
    • Start your localhost server by opening MAMP (I've installed this one) and start Apache Server service.
    • Browse to location: D:\Murez\Home\Project\Java\ChitChat\out\production\ChitChat\com\julisa\remote and copy EventListenable.class file. Then paste it on the target directory: C:\MAMP\htdocs\chitchat\com\julisa\remote. If the parent directories don't exist, create them.
    • Finally, run this app by pressing Ctrl + Shift + F10.
  4. Test!

I'm still wondering how to make a GIF in order to show you what activities I'm currently doing. As we know, recording screen activity will produce output with a large size. So, this is all I can present and then I will do better.


Curriculum

Proof of Work Done

Sort:  

I thank you for your contribution. Here is my thought;

  • Well done, @murez-nst. It's one of the best tutorials I've ever seen which covers advanced and complex concepts. I thank you for your valuable time which dedicated to writing this. I liked the way you describe your algorithm and what you're doing with it. I await your next posts. Good luck!

Your contribution has been evaluated according to Utopian policies and guidelines, as well as a predefined set of questions pertaining to the category.

To view those questions and the relevant answers related to your post, click here.


Need help? Write a ticket on https://support.utopian.io/.
Chat with us on Discord.
[utopian-moderator]

Your welcome @yokunjon, I like your response about my contribution. Hopefully my next tutorial will also be valuable for the open source community.

Hey @murez-nst
Thanks for contributing on Utopian.
We’re already looking forward to your next contribution!

Want to chat? Join us on Discord https://discord.gg/h52nFrV.

Vote for Utopian Witness!

Congratulations @murez-nst! You have completed the following achievement on Steemit and have been rewarded with new badge(s) :

Award for the total payout received

Click on the badge to view your Board of Honor.
If you no longer want to receive notifications, reply to this comment with the word STOP

Do not miss the last post from @steemitboard:
SteemitBoard World Cup Contest - Russia vs Croatia


Participate in the SteemitBoard World Cup Contest!
Collect World Cup badges and win free SBD
Support the Gold Sponsors of the contest: @good-karma and @lukestokes


Do you like SteemitBoard's project? Then Vote for its witness and get one more award!