Friday, May 29, 2020

Java - Generics

Java - Generics, Oracle Java Guides, Oracle Java Learning, Java Exam Prep, Java Prep

It would be nice if we could write a single sort method that could sort the elements in an Integer array, a String array, or an array of any type that supports ordering.

Java Generic methods and generic classes enable programmers to specify, with a single method declaration, a set of related methods, or with a single class declaration, a set of related types, respectively.

Generics also provide compile-time type safety that allows programmers to catch invalid types at compile time.

Using Java Generic concept, we might write a generic method for sorting an array of objects, then invoke the generic method with Integer arrays, Double arrays, String arrays and so on, to sort the array elements.

Generic Methods


You can write a single generic method declaration that can be called with arguments of different types. Based on the types of the arguments passed to the generic method, the compiler handles each method call appropriately. Following are the rules to define Generic Methods −

◉ All generic method declarations have a type parameter section delimited by angle brackets (< and >) that precedes the method's return type ( < E > in the next example).

◉ Each type parameter section contains one or more type parameters separated by commas. A type parameter, also known as a type variable, is an identifier that specifies a generic type name.

◉ The type parameters can be used to declare the return type and act as placeholders for the types of the arguments passed to the generic method, which are known as actual type arguments.

◉ A generic method's body is declared like that of any other method. Note that type parameters can represent only reference types, not primitive types (like int, double and char).

Example

Following example illustrates how we can print an array of different type using a single Generic method −

Live Demo
public class GenericMethodTest {
   // generic method printArray
   public static < E > void printArray( E[] inputArray ) {
      // Display array elements
      for(E element : inputArray) {
         System.out.printf("%s ", element);
      }
      System.out.println();
   }

   public static void main(String args[]) {
      // Create arrays of Integer, Double and Character
      Integer[] intArray = { 1, 2, 3, 4, 5 };
      Double[] doubleArray = { 1.1, 2.2, 3.3, 4.4 };
      Character[] charArray = { 'H', 'E', 'L', 'L', 'O' };

      System.out.println("Array integerArray contains:");
      printArray(intArray);   // pass an Integer array

      System.out.println("\nArray doubleArray contains:");
      printArray(doubleArray);   // pass a Double array

      System.out.println("\nArray characterArray contains:");
      printArray(charArray);   // pass a Character array
   }
}

This will produce the following result −

Output

Array integerArray contains:
1 2 3 4 5

Array doubleArray contains:
1.1 2.2 3.3 4.4

Array characterArray contains:
H E L L O

Bounded Type Parameters


There may be times when you'll want to restrict the kinds of types that are allowed to be passed to a type parameter. For example, a method that operates on numbers might only want to accept instances of Number or its subclasses. This is what bounded type parameters are for.

To declare a bounded type parameter, list the type parameter's name, followed by the extends keyword, followed by its upper bound.

Example

Following example illustrates how extends is used in a general sense to mean either "extends" (as in classes) or "implements" (as in interfaces). This example is Generic method to return the largest of three Comparable objects −

public class MaximumTest {
   // determines the largest of three Comparable objects
 
   public static <T extends Comparable<T>> T maximum(T x, T y, T z) {
      T max = x;   // assume x is initially the largest
     
      if(y.compareTo(max) > 0) {
         max = y;   // y is the largest so far
      }
     
      if(z.compareTo(max) > 0) {
         max = z;   // z is the largest now               
      }
      return max;   // returns the largest object 
   }
 
   public static void main(String args[]) {
      System.out.printf("Max of %d, %d and %d is %d\n\n",
         3, 4, 5, maximum( 3, 4, 5 ));

      System.out.printf("Max of %.1f,%.1f and %.1f is %.1f\n\n",
         6.6, 8.8, 7.7, maximum( 6.6, 8.8, 7.7 ));

      System.out.printf("Max of %s, %s and %s is %s\n","pear",
         "apple", "orange", maximum("pear", "apple", "orange"));
   }
}

This will produce the following result −

Output

Max of 3, 4 and 5 is 5

Max of 6.6,8.8 and 7.7 is 8.8

Max of pear, apple and orange is pear

Generic Classes


A generic class declaration looks like a non-generic class declaration, except that the class name is followed by a type parameter section.

As with generic methods, the type parameter section of a generic class can have one or more type parameters separated by commas. These classes are known as parameterized classes or parameterized types because they accept one or more parameters.

Example

Following example illustrates how we can define a generic class −

public class Box<T> {
   private T t;

   public void add(T t) {
      this.t = t;
   }

   public T get() {
      return t;
   }

   public static void main(String[] args) {
      Box<Integer> integerBox = new Box<Integer>();
      Box<String> stringBox = new Box<String>();
   
      integerBox.add(new Integer(10));
      stringBox.add(new String("Hello World"));

      System.out.printf("Integer Value :%d\n\n", integerBox.get());
      System.out.printf("String Value :%s\n", stringBox.get());
   }
}

This will produce the following result −

Output

Integer Value :10
String Value :Hello World

Thursday, May 28, 2020

Java - Data Structures

Oracle Java Tutorial and Material, Oracle Java Exam Prep, Oracle Java Guides, Oracle Java Learning

The data structures provided by the Java utility package are very powerful and perform a wide range of functions. These data structures consist of the following interface and classes −

◉ Enumeration
◉ BitSet
◉ Vector
◉ Stack
◉ Dictionary
◉ Hashtable
◉ Properties

All these classes are now legacy and Java-2 has introduced a new framework called Collections Framework, which is discussed in the next chapter. −

The Enumeration


The Enumeration interface isn't itself a data structure, but it is very important within the context of other data structures. The Enumeration interface defines a means to retrieve successive elements from a data structure.

For example, Enumeration defines a method called nextElement that is used to get the next element in a data structure that contains multiple elements.

The BitSet


The BitSet class implements a group of bits or flags that can be set and cleared individually.

This class is very useful in cases where you need to keep up with a set of Boolean values; you just assign a bit to each value and set or clear it as appropriate.

The Vector


Oracle Java Tutorial and Material, Oracle Java Exam Prep, Oracle Java Guides, Oracle Java Learning
The Vector class is similar to a traditional Java array, except that it can grow as necessary to accommodate new elements.

Like an array, elements of a Vector object can be accessed via an index into the vector.

The nice thing about using the Vector class is that you don't have to worry about setting it to a specific size upon creation; it shrinks and grows automatically when necessary.

The Stack


The Stack class implements a last-in-first-out (LIFO) stack of elements.

You can think of a stack literally as a vertical stack of objects; when you add a new element, it gets stacked on top of the others.

When you pull an element off the stack, it comes off the top. In other words, the last element you added to the stack is the first one to come back off.

The Dictionary


The Dictionary class is an abstract class that defines a data structure for mapping keys to values.

This is useful in cases where you want to be able to access data via a particular key rather than an integer index.

Since the Dictionary class is abstract, it provides only the framework for a key-mapped data structure rather than a specific implementation.

The Hashtable


The Hashtable class provides a means of organizing data based on some user-defined key structure.

For example, in an address list hash table you could store and sort data based on a key such as ZIP code rather than on a person's name.

The specific meaning of keys with regard to hash tables is totally dependent on the usage of the hash table and the data it contains.

The Properties


Properties is a subclass of Hashtable. It is used to maintain lists of values in which the key is a String and the value is also a String.

The Properties class is used by many other Java classes. For example, it is the type of object returned by System.getProperties( ) when obtaining environmental values.

Wednesday, May 27, 2020

JFileChooser Example - Show Open Save File Dialog in Java Swing Application

Oracle Java Exam Prep, Oracle Java Learning, Oracle Java Guides, Oracle Java Swing Application

Hello guys, if you worked in Java GUI based application then you may know that Swing provides class javax.swing.JFileChooser that can be used to present a dialog for the user to choose a location and type a file name to be saved, using the showSaveDialog() method. Syntax of this method is as follows:

public int showSaveDialog(Component parent)

where the parent is the parent component of the dialog, such as a JFrame.
Once the user typed a file name and select OK or Cancel, the method returns one of the following value:

◉ JFileChooser.CANCEL_OPTION: the user cancels file selection.

◉ JFileChooser.APPROVE_OPTION: the user accepts file selection.

◉ JFileChooser.ERROR_OPTION: if there’s an error or the user closes the dialog by clicking on the X button.

After the dialog is dismissed and the user approved a selection, you can use the getSelectedFile() methods to get the selected file:

Also, it's worth noting that before calling the showSaveDialog() method, you may want to set some options for the dialog. You can use the setDialogTitle(String)  method to set a custom title text for the dialog and setCurrentDirectory(File) to set the directory where it will be saved.

Java Program to use JFileChooser in Swing


Here is a complete Java program that demonstrates how to use the file chooser class from the Swing package. You can just copy-paste the code and run in your favorite IDE like Eclipse, NetBeans, or IntelliJIDEA. You don't need to add any third-party library because swing comes along JDK itself.

package test;

import java.awt.BorderLayout;
import java.awt.FlowLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.io.File;
import javax.swing.JButton;
import javax.swing.JFileChooser;
import javax.swing.JFrame;
import javax.swing.SwingUtilities;
import javax.swing.UIManager;
import javax.swing.UnsupportedLookAndFeelException;

/**
* Java program to demonstrate how to use JFileChooser for showing open file
* dialog and save file dialog in Java application.
 */
public class Test extends JFrame {

    private final JButton upload = new JButton("Upload");
    private final JButton save = new JButton("Save");
 

    public Test() {
        super("JFileChooser Example - Open Save File Dialong in Java");
        setLayout(new FlowLayout());
        upload.addActionListener(new ActionListener() {

            @Override
            public void actionPerformed(ActionEvent arg0) {
                showSelectFileDialong();
            }
        });
        getContentPane().add(upload);
        setSize(300, 100);
        setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        setVisible(true);
    }

    public static void main(String[] args) {
        try {
            UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
        } catch (ClassNotFoundException
                   | InstantiationException
                   | IllegalAccessException
                   | UnsupportedLookAndFeelException e) {
        }

        SwingUtilities.invokeLater(new Runnable() {

            @Override
            public void run() {
                Test t = new Test();
            }
        });
    }

    private void showSelectFileDialong() {
        JFileChooser fileChooser = new JFileChooser();
        fileChooser.setDialogTitle("Choose a File to upload");

        // pass reference of your JFrame here
        int response = fileChooser.showSaveDialog(this);
        if (response == JFileChooser.APPROVE_OPTION) {
            File selectedFile = fileChooser.getSelectedFile();
            System.out.println("Save as file: "
                      + selectedFile.getAbsolutePath());
        }
    }

}

How to run this Java Swing Program?


As I said, this program is complete in itself. You can just copy paste and run in your favorite IDE. Just make sure that you keep the name of the source file the same as the public class. If you use Eclipse then you don't even need to do that because Eclipse will automatically create the file with the same name.

You also don't need any third-party library because Swing is part of JDK. When you run this program it will start a Swing application and you will see a JFrame window as shown below

Oracle Java Exam Prep, Oracle Java Learning, Oracle Java Guides, Oracle Java Swing Application

You can see the upload button, when you click on the button it will open the native file chooser option which will allow you to choose the file and folder you want to upload as shown in the following screenshot:

Oracle Java Exam Prep, Oracle Java Learning, Oracle Java Guides, Oracle Java Swing Application

That's all about how to use the JFileChooser class in Java Swing application. It's one of the most useful and common components of Java Swing API and you will often find using it Java GUI application. Just make you understand the different API methods to customize the look and feel of the window and also how to get the name and other attributes of the chosen file to do the next action like saving into a database or any other directory.

Tuesday, May 26, 2020

Java 8 Lambda Expressions with examples and Rules

Java 8 Lambda Expressions, Oracle Java Tutorial and Material, Oracle Java Learning, Oracle Java Exam Prep

Java 8 is first released in 2014 and introduced lot of new features. Lambda expressions are the most significant change in java 8 version.

As of java 7, Oracle/Sun people have given impartance to the object oriented programming languages but in java 8, they have introduced functional programming to compete with other programming languages such as Scala, C# etc.

What is Lambda Expression?


Any function which is having no name is called as Lambda expression. Which is also called as anonymous function.

Rules:

1) function should not have access modifier
2) Should not have any return type (even void also not allowed)
3) Should not have name for function
4) Should use arrow symbol "->"

We will see now a few examples how to convert normal java fucntions to lambda expression.

Example 1:

Before java 8:

public void print() {
  System.out.println("Hello World");
 }

In java 8:

() -> {
  System.out.println("Hello World");
   };

Please observe here, we have remvoed fucntion access modifier (public), return type(void) and method name (print) in the lambda expression and added -> symbol. Note: If method body has only statement then curly braces are optional. Curly braces are mandetory if multiple statements are present in the method body. we can rewrite the above lambda expression as below.

() -> System.out.println("Hello World");

Example 2:

Before java 8:

public void sum(int a, int b) {
  System.out.println("sum :" + (a + b));
 }

In java 8:

(int a, int b) -> System.out.println("sum :" + (a + b));

Example 3: Finding the length of string Before java 8:

public int getLength(String value) {
  return value.length();
 }

In java 8:

(String value) -> {
   return value.length();
  }; 

Java 8 Lambda Expressions, Oracle Java Tutorial and Material, Oracle Java Learning, Oracle Java Exam Prep

Lambda Expressions Thumb Rules:


1) Parameters are optional. It can be zero or more.

() -> {System.out.println("Hello World");};
(int a) -> {System.out.println("value "+a);};

2) If no parameters are available then need to use empty parenthesis ().

() -> {System.out.println("Hello World");};

3) If we have multiple parameters then need to separate them with comma(,)

(int a, int b) -> System.out.println("sum "+(a+b));

4) if body has only statement then curly braces are optional.

(int a) -> System.out.println("value "+a);

5) if body has more than one statement then curly braces are mandetory.

() -> {
   System.out.println("Hello World");};
   System.out.println("value "+a);
    };
 
6) parameter type(s) is optional. No need to declare manually because compile can expect based on the context. We will disscuss more in the next post.

(a) -> System.out.println("value "+a);
(a, b) -> System.out.println("sum "+(a+b));

7) If only one parameter is available then parenthesis are optional.

(int a) -> {System.out.println("value "+a);};

The above can be rewritten as a -> {System.out.println("value "+a);};

Sunday, May 24, 2020

Why use Cloud Computing? Advantages and Disadvantages

Cloud Computing, Oracle Java Tutorial and Material, Oracle Java Guides, Oracle Java Certifications

Cloud Computing has been a buzz word in the IT world from the last few years. When it first appeared, like many things, a lot of people has dismissed it as being next big thing, but cloud computing has certainly lived up to expectation and truly shift how Information technology arm of business function today.

Cloud Computing is made of two terms Cloud and Computing, the first term refers its flexibility in terms of moving around and scaling up (or down) on the fly, without affecting end-users, rather like a cloud; Second term is obviously more obvious, it refers to taking advantage of computers for real-world calculations. In fact, cloud computing is similar to distributed computing, where is program runs on many connected computers to produce a result.

Types of Cloud Computing


Cloud computing comes in different flavors which are tailored based upon the client’s need. Some companies might just want to use clouds for infrastructure, some may want hardware as well as software, some just want software, and others may want everything.

By the way, SasS (software as a service) and PasS (platform as a service) are two of the most popular cloud computing services.

SaaS (software as a service)

PaaS (platform as a service)

IaaS (infrastructure as a service)

HaaS (hardware as a service)

EaaS (Everything as a service).

Cloud Computing, Oracle Java Tutorial and Material, Oracle Java Guides, Oracle Java Certifications

Advantages of Cloud Computing


Here are some of the main advantages of cloud computing, which will help you to decide whether to move onto clouds or not.

1) No worry to maintain Infrastructure

2) Cost Benefits

3) Backup and Recovery

4) Reliability

5) Scalability

6) Convenience

7) Environment Friendly

8) Resiliency, Redundancy and High Availability

9) Performance

10) Reduced Time to market

11) Increased Storage Capacity

12) Location Independence

Disadvantages of Cloud Computing


There is nothing like free lunch in this world. Everything has advantages and disadvantages and cloud computing also not different. In the last section, we have seen some big benefits of moving to clouds but let’s now analyze some cons of cloud computing.

1) Regulatory Requirement

2) Privacy and Security of Confidential data

3) Latency

4) Vendor Dependency

6) Limited or No Control

7) Increased Vulnerability

That’s all about the  advantages and disadvantages of cloud computing. You can see cloud computing has serious pros to offers and the way it’s going, I am sure the list of cons will be reduced. cloud computing’s growth is natural and it’s coming from the real need of people and small scale companies, start-ups who cannot afford to set up and run their own data centers, and in many cases only need high availability, recovery, and resiliency for their services.

If they turned out lucky and need to scale quickly, they can easily do it by using trusted and matured cloud computing companies like Amazon web services. It’s a win for both service providers and customers, and work best for biggies like Amazon and Google, who has built their cloud infrastructure for their own personal need, rather than keeping cloud computing business in their mind.

In short, Cloud computing offers a cost-effective solution for infrastructure, platform and software need of people and companies which need aims to provide the same level of services like biggies, but can’t afford massive data center costs.

Friday, May 22, 2020

5 Ways to implement Singleton Design Pattern in Java

Oracle Java Tutorial and Material, Oracle Java Study Material, Oracle Java Learning, Oracle Java Exam Prep

This article introduces the singleton design pattern and its 5 implementation variations in Java You will learn different ways to implement a singleton pattern and also understand the challenges with creating a singleton class like thread-safety issues and serialization issues.

Problem


At most one instance of a class must be created in an application.

Solution


That class (singleton) is defined including its own instance, and the constructor must be private.

What is Singleton Design Pattern in Java?


Singleton Design Pattern is a popular pattern to keep a global variable. It's a class with a getInstance() method which is both public and static so that any class which needs a reference to this class can call this method and get the global reference.

Here is the UML diagram of Singleton design pattern in Java and then we'll see how you can implement Singleton pattern in Java:

Oracle Java Tutorial and Material, Oracle Java Study Material, Oracle Java Learning, Oracle Java Exam Prep

1. Lazy initialization, non-thread-safe:


This is the classical version, but it's not thread-safe. If more than one thread attempts to access instance at the same time, more than one instance may be created.

public class Singleton {
    private static Singleton instance = null;
    public static Singleton Instance() {
        if (instance == null) {
            instance = new Singleton();
        }
        return instance;
    }
    private Singleton() {}
}

2. Non-lazy initialization, thread-safe


This is the simplest thread-safe version, but it does not support lazy initialization.

public class Singleton {
    private static volatile  Singleton instance = new Singleton();
    public static Singleton Instance() {
        return instance;
    }
    private Singleton() {}
}

3. Lazy initialization, thread-safe


This version supports both properties but has performance problems. Once a thread uses a singleton instance, the others have to wait because of the lock.

public class Singleton {
    private static Singleton instance = null;
    private static readonly object lockObj = new object();
    public static Singleton Instance() {
        lock (lockObj) {
            if (instance == null) {
                instance = new Singleton();
            }
            return instance;
        }
    }
    private Singleton() {}
}

4. Double-check locking


An improved version of the third solution. Two null controls prevent lock waits for the most time, but not always. Also, it does not work properly for Java because of the Java memory management approach.

public class Singleton {
    private static Singleton instance = null;
    private static object lockObj = new object();
    public static Singleton Instance() {
        if (instance == null) {
            lock (lockObj) {
                if (instance == null) {
                    instance = new Singleton();
                }
            }
        }
        return instance;
    }
    private Singleton() {}
}

5. Nested initialization


A nested class is used for lazy initialization. This version is also thread-safe, but a bit complex. For most situations, solutions 2 or 4 will be suitable according to performance parameters.

public class Singleton {
    public static Singleton Instance() {
         return Nested.instance;
    }
    private Singleton() {}

    class Nested {
        static Nested() {}
        internal static readonly Singleton instance = new Singleton();
    }
}

Usage:


public static void main (string[] args) {
    Singleton instance = Singleton.Instance();
}

That's all about how to implement the Singleton Design Pattern in Java. I have shown you 5 different implementations of the Singleton pattern in Java. You can use any of the above methods to create Singleton for your project, though I recommend Enum Singleton pattern because of its simplicity and full proof nature.

Thursday, May 21, 2020

A Java XMPP Load Test Tool

Oracle Java Tutorial and Material, Oracle Java Certification, Oracle Java Exam Prep, Oracle Java Guides

In this article, we will develop an XMPP Load Test Tool written in Java.

1. Introduction


The eXtensible Messaging and Presence Protocol (XMPP) is a communication protocol for message-oriented middleware based on XML (Extensible Markup Language). It is an open protocol standardized by the Internet Engineering Task Force (IETF) and supported and extended by the XMPP Standards Foundation (XSF). XMPP is defined in an open standard and uses an open systems approach of development and application. Hence, many server, client, and library implementations are distributed as free and open-source software. There are also a number of extensions defined in XMPP Extension Protocols (XEPs).

One such free and open-source distribution is the one from IgniteRealtime which provides the following implementations:

◉ Openfire chat server
◉ Spark chat client
◉ Smack java library of the XMPP protocol

Spark is a chat client application similar to Messenger, What’s app, Viber or Google Talk (actually the latter uses the XMPP protocol). One can send chat messages, files as attachments etc. These are sent to the Openfire server which then takes care of delivering them to their destinations, which can be another Spark (or other) chat client that connects directly to it, or to another Openfire instance (federation) until they reach their final destination.

However, how does the server and client perform under load, i.e. when they have to deal with many chat messages or many file transfers?

2. XMPP Load Test tools


A number of solutions exist to load/stress test an XMPP server like Openfire (list is not exhaustive):

◉ Apache JMeter with the XMPP Protocol Support plugin (see [1,2])
◉ iksemel XMPP C library
◉ Tsung, an open-source multi-protocol distributed load testing tool

In this article we will write an XMPP Load Test tool in Java using the Smack XMPP library.

3. Prerequisites


You need to download and install Openfire on your system. To try our load test tool, using an embedded database is sufficient, even though you need to keep in mind that the embedded database (HSQLDB) will fill up after some time. Using a real RDBMS is of course the recommended way.

You must create a number of users that will simulate the user message exchange load. In our example we will create 50 users with usernames user001 to user050 all having the same password, a. If you don’t know how to do that, login to the admin console (e.g. http://localhost:9090, or https://localhost:9091), and click on the Users/Groups tab; there you can click on Create New User to create a user.

Since the creation of a big number of users is rather tedious, there are a couple of plugins that may save you time. Click on the Plugins tab of the Openfire admin console, then on Available Plugins and install the User Creation and/or User Import/Export plugin(s). If you now click back to the Users/Groups tab, you will see that new links have been created; Users Creation (thanks to the User Creation plugin) and Import & Export (due to the User Import/Export plugin). It is left as an exercise to find out how they work.

However, these are not the only changes one needs to do. The security mechanism has been changed in the latest versions of Openfire, so for our program to work, we need to define two properties. Click on the Server tab, Server Manager -> System Properties and on the bottom of the page enter the following property name/value pairs:

sasl.mechs.00001 PLAIN
sasl.mechs.00002 DIGEST-MD5

4. LoadXmppTest Java program


The tool that we will create is a Java program that uses the smack library. It provides a Command Line Interface (CLI), but you may write a Graphical User Interface (GUI) for it if you find it useful.

$ java -Djava.util.logging.config.file=logging.properties -jar loadxmpptest.jar
Required options: s, d, p, n
usage: java -Djava.util.logging.config.file=logging.properties –jar loadxmpptest.jar
-a,--attachment  Test attachments
-b,--big         Test big attachments or messages
-d,--domain      Domain
-n,--number      Number of users
-o,--observer    Observer
-p,--password    Password
-s,--server      Server
Usage : java -Djava.util.logging.config.file=logging.properties -jar loadxmpptest.jar -s  -d  -p  -n  [-o ] [-a] [-b]
        jabber id : userXXX@
        chatroom  : roomXXX@conference.
        observer  : userXXX@/Spark (just to test)
        10 users per chatroom
        5 chatrooms
Use:
        -a to test small attachments (file transfers) or
        -a -b to test big attachments (file transfers)
or:
        -b to test long messages

Additionally, loadxmpptest.properties allows to further configure the test application:

SHORT_MESSAGES_DELAY_SECONDS = 100
LONG_MESSAGES_DELAY_SECONDS = 60
SMALL_ATTACHMENTS_DELAY_MINUTES = 1
BIG_ATTACHMENTS_DELAY_MINUTES = 5
DELAY_TO_SEND_MESSAGES_MILLISECONDS = 1000
BIG_FILE_NAME_PATH=blob.txt
SMALL_FILE_NAME_PATH=test.txt

Logging is stored in log/loadxmpptest.log and can be configured by editing logging.properties.

Here’s an example of execution where the server is localhost, the domain is localhost (could be something else), 50 users are emulated all with the same password a:

java -Djava.util.logging.config.file=logging.properties -jar loadxmpptest.jar -s localhost -d localhost -p a -n 50

Another example, this time sending big attachments:

java -Djava.util.logging.config.file=logging.properties -jar loadxmpptest.jar -s localhost -d localhost -p a -n 50 -ba

The files to be sent are configured in loadxmpptest.properties as mentioned above.

4.1 Create a new Maven project

Jump to your favourite IDE and create a new Maven project. Add the following dependencies to the pom.xml:

<dependencies>
    <dependency>
        <groupId>org.igniterealtime.smack</groupId>
        <artifactId>smack-core</artifactId>
        <version>4.3.4</version>
    </dependency>
    <dependency>
        <groupId>org.igniterealtime.smack</groupId>
        <artifactId>smack-tcp</artifactId>
        <version>4.3.4</version>
    </dependency>
    <dependency>
        <groupId>org.igniterealtime.smack</groupId>
        <artifactId>smack-im</artifactId>
        <version>4.3.4</version>
    </dependency>   
    <dependency>
        <groupId>org.igniterealtime.smack</groupId>
        <artifactId>smack-extensions</artifactId>
        <version>4.3.4</version>
    </dependency>
    <dependency>
        <groupId>org.igniterealtime.smack</groupId>
        <artifactId>smack-java7</artifactId>
        <version>4.3.4</version>
    </dependency>
    <dependency>
        <groupId>org.igniterealtime.smack</groupId>
        <artifactId>smack-debug</artifactId>
        <version>4.3.4</version>
    </dependency>
    <dependency>
        <groupId>commons-cli</groupId>
        <artifactId>commons-cli</artifactId>
        <version>1.4</version>
    </dependency>
</dependencies>

These are the latest versions at the time of writing article, but you can use the latest versions you may find in Maven Central.

4.2 Create the main class

The program consists of two classes based on [10]. XmppLoadTest contains the main() method and delegates to XmppManager to do the work (contrary to the reality as the norm is that managers delegate instead of actually doing the work :) ).

public static void main(String[] args) throws Exception {
    parseCLIArguments(args);
    final XmppLoadTest loadXmppTest = new XmppLoadTest();
    loadProperties(PROPERTIES_FILE);
    init(loadXmppTest);
    performLoad(loadXmppTest);
}

I ‘ll skip the description of the parseCLIArguments() method. It uses the Apache Commons CLI library to parse the command line arguments (see [4]). You may choose any other CLI library or create a GUI if you like.

Our test simulates 50 users and 5 chat rooms (you can simulate your own scenario to match your needs). These are stored in:

private static final List<User> users = new ArrayList< >(numberOfUsers);
private static final List<ChatRoom> chatRooms = new ArrayList< >(numberOfRooms);

The classes User and ChatRoom are defined like so:

/**
  * User (e.g. {@code user001}). Functionality delegated to @{see
  * XmppManager}.
  */
final class User {
        private final String username;
        private final String password;
        private final String domain;
        private final XmppManager xmppManager; // delegate to it
        private MultiUserChat joinedChatRoom;
        public User(String username, String password, String domain, XmppManager xmppManager) {
            this.username = username;
            this.password = password;
            this.domain = domain;
            this.xmppManager = xmppManager;
        }
        public String getUsername() { return username; }
        public String getPassword() { return password; }
        public String getJabberID() { return username + "@" + domain; }
        public void connect() {
            xmppManager.connect();
        }
        public void disconnect() {
            xmppManager.destroy();
            LOG.info("User " + username + " disconnected.");
        }
        public void login() {
            xmppManager.login(username, password);
        }
        public void setStatus(boolean available, String status) {
            xmppManager.setStatus(available, status);
        }
        public void sendMessage(String toJID, String message) {
            xmppManager.sendMessage(toJID, message);
        }
        public void receiveMessage() {
            xmppManager.receiveMessage();
        }
        public void sendAttachment(String toJID, String path) {
            xmppManager.sendAttachment(toJID, "Smack", path);
        }
        public void receiveAttachment() {
            xmppManager.receiveAttachment(username);
        }
        public void joinChatRoom(String roomName, String nickname) {
            joinedChatRoom = xmppManager.joinChatRoom(roomName, nickname);
        }
        public void leaveChatRoom() {
            try {
                joinedChatRoom.leave();
            } catch (SmackException.NotConnectedException | InterruptedException ex) {
                LOG.severe(ex.getLocalizedMessage());
            }
        }
        public void sendMessageToChatRoom(String message) {
            xmppManager.sendMessageToChatRoom(joinedChatRoom, message);
        }
        public String getJoinedChatRoom() {
            return joinedChatRoom.getRoom().toString();
        }
        public void addRosterListener() {
            xmppManager.rosterChanged();
        }
    }
    /**
     * Chat room, e.g. {@code room001}
     */
    final class ChatRoom {
        private final String name;
        private final String domain;
        public ChatRoom(String name, String domain) {
            this.name = name;
            this.domain = domain;
        }
        public String getName() {
            return name + "@conference." + domain;
        }
    }

The ChatRoom class is straightforward. A chat room is identified as e.g. room001@conference.localhost, where conference is the subdomain defined in the Openfire administrator console when you click on Group Chat -> Group Chat Settings and localhost is the domain we passed with the command line argument -d. The String returned by the getName() is the bare JID of the room, as we will see later.

The User class is more complex. It requires a username, a password and a domain and delegates to XmppManager as we will see shortly.

XMPP clients have addresses of the form user@server.com where user is the username and server.com is the domain. A node address in XMPP is called a Jabber ID abbreviated as JID. A JID can also have a resource (user@server.com/resource) which means that the user can connect from multiple devices. A JID of the form user@server.com is called a bare JID, while a JID of the form user@server.com/resource is called a full JID.

A user can connect() to an Openfire server, then login() and then the user can setStatus(), can sendMessage()/receiveMesage(), sendAttachment()/receiveAttachment(), joinChatRoom()/leaveChatRoom() and sendMessageToChatRoom().

The init() method initializes XmppManager() and creates 50 users, each one connects, logins and sets its status to available. If file transfers are to be tested, then each user starts listening to file transfers. Five chat rooms are created, too. Each one of the 50 users is distributed to a chatroom, so that in the end, every chat room contains 10 users.

private static void init(XmppLoadTest loadXmppTest) {
    XmppManager xmppManager = new XmppManager(server, domain, port);
    for (int i = 1; i <= numberOfUsers; i++) {
        User user = loadXmppTest.new User("user" + String.format("%03d", i), password, domain, xmppManager);
        user.connect();
        user.login();
        user.setStatus(true, "Hello from " + user.getUsername());
        users.add(user);
        if (testAttachments || testBigAttachments) {
                user.receiveAttachment();
        }
    }
    for (int i = 0; i < numberOfRooms; i++) {
        chatRooms.add(loadXmppTest.new ChatRoom("room" + String.format("%03d", i + 1), domain));
    }
    if (!testAttachments && !testBigAttachments) {
        // join chatrooms
        for (int i = 1; i <= numberOfUsers; i++) {
            ChatRoom chatRoom = chatRooms.get((i - 1) % numberOfRooms);
            User user = users.get(i - 1);
            user.joinChatRoom(chatRoom.getName(), user.getJabberID());
        }
    }
}

One scenario is for each user to connect to one of the five chat rooms and send messages. A task is created (chatRoomMessageTask) in performLoad() and is executed every every seconds depending on the type of message (long or short) as configured in loadxmpptest.properties.

ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(1);
....
        } else { // send messages to chat rooms
    final Runnable task = () -> {
        while (true) {
            if (Thread.currentThread().isInterrupted()) {
                return;
            }
            loadXmppTest.chatRoomMessageTask();
        }
    };
    int every = testLongMessages ? longMessagesDelayInSeconds : shortMessagesDelayInSeconds;
    scheduler.scheduleWithFixedDelay(task, 0, every, SECONDS); // every x seconds
}

The other scenario is to send attachments to another user instead of sending messages to a chat room:

if (testAttachments || testBigAttachments) {  // send attachments
   String filePath  = testBigAttachments ? bigFileNamePath : smallFileNamePath;
   int delay = testBigAttachments ? bigAttachmentsDelayInMinutes : smallAttachmentsDelayInMinutes;
   final Runnable task = () -> {
       while (true) {
           if (Thread.currentThread().isInterrupted()) {
               return;
           }
           loadXmppTest.fileTransferTask(filePath);
       }
   };
 scheduler.scheduleWithFixedDelay(task, 0, delay, MINUTES);

You may of course combine the two scenarios but you need to make sure that you don’t overflow the caches of Openfire.

/** Each user sends a message to a chat room. */
private synchronized void chatRoomMessageTask() {
    for (int i = 1; i <= numberOfUsers; i++) {
        String message = testLongMessages ? LONG_MESSAGE : MESSAGE;
        User user = users.get(i - 1);
        try {
            Thread.currentThread().sleep(delayToSendMessagesInMillis); // sleep 1"
            user.sendMessageToChatRoom(message);
            LOG.info(user.getJabberID() + " sent " + (testLongMessages ? "long" : "short") + " message to " + user.getJoinedChatRoom());
        } catch (InterruptedException ie) {
            Thread.currentThread().interrupt(); // reset the flag
        }
    }
}

In the above method, called the first scenario, each user sends a message (short or long) to the chatroom the user has joined to.

In the fileTransferTask(), each user sends an attachment to another user (avoiding to send one to themselves). Please pay attention to the synchronized keyword in this and the previous method to avoid deadlocks in the code.

/**
 * Exchange file attachments between users.
 *
 * @param path path of the file to send
 * @see #transferFile(int, java.lang.String)
 */
private void fileTransferTask(String path) {
    for (int i = 1; i <= numberOfUsers; i++) {
        transferFile(i, path);
    }
}
/**
 * Transfer the file to all other users.
 *
 * @param i i-th user
 * @param path path of the file to be sent
 */
private synchronized void transferFile(int i, String path) {
    int j;
    for (j = 1; j <= numberOfUsers; j++) {
        if (i != j) {
            try {
                int delay = testBigAttachments ? bigAttachmentsDelayInMinutes : smallAttachmentsDelayInMinutes;
               Thread.currentThread().sleep(delay); 
               if (users.get(i - 1).sendAttachment(users.get(j - 1).getJabberID(), path)) {
                 LOG.info("Attachment " + path + " sent from " + users.get(i - 1).getJabberID() + " to " + users.get(j - 1).getJabberID());
               } else {
                 LOG.severe("Attachment " + path + " from " + users.get(i - 1).getJabberID() + " to " + users.get(j - 1).getJabberID() + "  was not sent!");
               }
            } catch (InterruptedException ie) {
                Thread.currentThread().interrupt(); // reset the flag
            }
        }
    }
}

This concludes the description of the XmppLoadTest class.

4.3 XmppManager class

The XmppManager class uses the smack library [6, 7] to communicate with the Openfire server. Smack is a library for communicating with XMPP servers to perform real-time communications, including instant messaging and group chat.

XmppManager is similar to the one in [10], but things have evolved till then and the API has changed. As already mentioned, User delegates to XmppManager.

4.3.1 Connect to Openfire

To connect to an Openfire server you need the name of the server that hosts the Openfire, the domain and the port (which is fixed: 5222). The XMPPTCPConnection class is used to create a connection to an XMPP server. Further connection parameters can be configured by using a XMPPTCPConnectionConfiguration.Builder:

private String resource = "Smack";
...
XMPPTCPConnectionConfiguration.Builder builder =  XMPPTCPConnectionConfiguration.builder();
try {
    builder.setXmppDomain(JidCreate.domainBareFrom(domain))
            .setHost(server)
            .setPort(port)
            .setResource(resource)
            .setSecurityMode(SecurityMode.disabled)
            .setHostnameVerifier((String hostname, SSLSession session) -> true);
} catch (XmppStringprepException ex) {
    LOG.severe(ex.getLocalizedMessage());
}
try {
    builder = TLSUtils.acceptAllCertificates(builder);
} catch (KeyManagementException | NoSuchAlgorithmException ex) {
    LOG.log(Level.SEVERE, null, ex);
}
XMPPTCPConnection.setUseStreamManagementDefault(true);
XMPPTCPConnectionConfiguration config = builder.build();

The resource String is important for file transfers. If you are using smack, it can be either "Smack" or "Resource". If you use another client, e.g. Spark, then you can set it to "Spark“. It allows to identify which resource to send the file to.

//SASLMechanism mechanism = new SASLDigestMD5Mechanism();
SASLMechanism mechanism = new SASLPlainMechanism();
SASLAuthentication.registerSASLMechanism(mechanism);
SASLAuthentication.unBlacklistSASLMechanism("PLAIN");
SASLAuthentication.blacklistSASLMechanism("SCRAM-SHA-1");
SASLAuthentication.unBlacklistSASLMechanism("DIGEST-MD5");        
 
try {
    builder = TLSUtils.acceptAllCertificates(builder);
} catch (KeyManagementException | NoSuchAlgorithmException ex) {
    LOG.severe(ex.getLocalizedMessage());
}
XMPPTCPConnection.setUseStreamManagementDefault(true);
XMPPTCPConnectionConfiguration config = builder.build();

The TLSUtils.acceptAllCertificates(builder); is important since the security model has changed in the latest versions of Openfire. For that reason, we added the sasl.mechs.00001 & sasl.mechs.00002 in the administration console of Openfire. If he still encounter connection/authentication issues, this link maybe of help.

4.3.2 Login

Once you have configured the connection to Openfire, it is time to connect to it:

private AbstractXMPPConnection connection;
...
connection = new XMPPTCPConnection(config);
connection.setReplyTimeout(1000L);
try {
     connection.connect();
} catch (SmackException | IOException | XMPPException | InterruptedException ex) {
     LOG.severe(ex.getLocalizedMessage());
}

By default Smack will try to reconnect the connection in case it was abruptly disconnected. The reconnection manager will try to immediately reconnect to the server and increase the delay between attempts as successive reconnections keep failing. Once a connection has been created, a user should login with the XMPPConnection.login() method using their credentials:

public void login(String username, String password) {
    if (connection != null && connection.isConnected()) {
        try {
            connection.login(username, password);
        } catch (XMPPException | SmackException | IOException | InterruptedException ex) {
            LOG.severe(ex.getLocalizedMessage());
        }
    }
    LOG.info(username + " authenticated? " + connection.isAuthenticated());
}

4.3.3 Presence and Roster

Once a user has logged in, they can begin chatting with other users by creating new Chat or MultiUserChat objects. The user can also set its status to available:

public void setStatus(boolean available, String status) {
    Presence.Type type = available ? Type.available : Type.unavailable;
    Presence presence = new Presence(type);
    presence.setStatus(status);
    try {
        connection.sendStanza(presence);
    } catch (SmackException.NotConnectedException | InterruptedException ex) {
        LOG.severe(ex.getLocalizedMessage());
    }
}

Each message to the XMPP server from a client is called a packet or a stanza and is sent as XML. A stanza is the smallest piece of XML data a client can send to a server and vice versa in one package. The org.jivesoftware.smack.packet Java package contains classes that encapsulate the three different basic packet types allowed by XMPP (message, presence, and IQ). Each of these stanzas is handled differently by XMPP servers and clients. Stanzas have type attributes and these can be used to further differentiate stanzas [3].

The message stanza is meant to be used to send data between XMPP entities. It is fire and forget i.e. stanzas are not acknowledged by the receiving party. Usually when you send a message stanza from your client and no error of any kind is generated, you can assume that it has been sent successfully. Message stanzas can be of type “chat”, “groupchar”, “error” etc.

The presence stanza advertises the online status (network availability) of other entities. Presence works like subscription in XMPP. When you are interested in the presence of some JID, you subscribe to their presence, i.e. you tell the XMPP server “everytime this JID sends you a presence update, I want to be notified”. Of course, the server asks the JID holder if they accept to disclose their presence information to you. When they accept, the server remembers their decision and updates anyone subscribed to their presence whenever they change their online status. The term presence also means whether a user is online or not.

Finally, the IQ (Info/Query) stanza is used to get some information from the server (e.g. info about the server or its registered clients) or to apply some settings to the server.

In XMPP, the term roster is used to refer to the contact list. A user’s contact list is usually stored on the server. The roster lets you keep track of the availability (presence) of other users. Users can be organized into groups such as “Friends” and “Colleagues”, and then you discover whether each user is online or offline. The Roster class allows you to find all the roster entries, the groups they belong to, and the current presence status of each entry.

Every user in a roster is represented by a RosterEntry, which consists of:

◉ An XMPP address (e.g. john@example.com).
◉ A name you’ve assigned to the user (e.g. "John").
◉ The list of groups in the roster that the entry belongs to. If the roster entry belongs to no groups, it’s called an “unfiled entry”.

Every entry in the roster has presence associated with it. The Roster.getPresence(String user) method will return a Presence object with the user’s presence or null if the user is not online or you are not subscribed to the user’s presence. A user either has a presence of online or offline. When a user is online, their presence may contain extended information such as what they are currently doing, whether they wish to be disturbed, etc.

public Roster createRosterFor(String user, String name) throws Exception {
    LOG.info(String.format("Creating roster for buddy '%1$s' with name %2$s", user, name));
    Roster roster = Roster.getInstanceFor(connection);
    roster.createEntry(JidCreate.bareFrom(user), name, null);
    return roster;
}
public void printRosters() throws Exception {
    Roster roster = Roster.getInstanceFor(connection);
    Collection entries = roster.getEntries();
    for (RosterEntry entry : entries) {
        LOG.info(String.format("Buddy: %s", entry.getName()));
    }
}
public void rosterChanged() {
    Roster roster = Roster.getInstanceFor(connection);
    roster.addRosterListener(new RosterListener() {
        @Override
        public void presenceChanged(Presence presence) {
            LOG.info("Presence changed: " + presence.getFrom() + " " + presence);
            resource = presence.getFrom().getResourceOrEmpty().toString();
        }
        @Override
        public void entriesAdded(Collection clctn) {  }
        @Override
        public void entriesUpdated(Collection clctn) {  }
        @Override
        public void entriesDeleted(Collection clctn) {  }
    });
}

The presence information will likely change often, and it’s also possible for the roster entries to change or be deleted. To listen for changing roster and presence data, a RosterListener is used. To be informed about all changes to the roster, the RosterListener should be registered before logging into the XMPP server. It is important for file transfers to know if the resource of a recipient has changed, as described here.

4.3.4 Chat and MultiChat

You send and receive chat messages with the help of the ChatManager. Although individual messages can be sent and received as packets, it’s generally easier to treat the string of messages as a chat using the org.jivesoftware.smack.chat2.Chat class. A chat creates a new thread of messages between two users. The Chat.send(String) method is a convenience method that creates a Message object, sets the body using the String parameter and then sends the message.

/**
 * Send message to another user.
 *
 * @param buddyJID recipient
 * @param message to send
 */
public void sendMessage(String buddyJID, String message) {
    LOG.info(String.format("Sending message '%1$s' to user %2$s", message, buddyJID));
    try {
        Chat chat = ChatManager.getInstanceFor(connection).chatWith(JidCreate.entityBareFrom(buddyJID));
        chat.send(message);
    } catch (XmppStringprepException | SmackException.NotConnectedException | InterruptedException ex) {
        LOG.severe(ex.getLocalizedMessage());
    }
}
public void receiveMessage() {
    ChatManager.getInstanceFor(connection).addIncomingListener(
            (EntityBareJid from, Message message, Chat chat) -> {
                LOG.info("New message from " + from + ": " + message.getBody());
            });
}

To join a chat room (MultiUserChat) and send messages to it:

public MultiUserChat joinChatRoom(String roomName, String nick) {
    try {
        MultiUserChatManager manager = MultiUserChatManager.getInstanceFor(connection);
        MultiUserChat muc = manager.getMultiUserChat(JidCreate.entityBareFrom(roomName));
        Resourcepart nickname = Resourcepart.from(nick);
        muc.join(nickname);
       LOG.info(muc.getNickname() + "joined chat room " + muc.getRoom());
        return muc;
    } catch (XmppStringprepException | SmackException.NotConnectedException | InterruptedException | SmackException.NoResponseException | XMPPException.XMPPErrorException | MultiUserChatException.NotAMucServiceException ex) {
        LOG.severe(ex.getLocalizedMessage());
    }
    return null;
}
 
public void sendMessageToChatRoom(MultiUserChat muc, String message) {
    try {
        muc.sendMessage(message);
        LOG.fine("Message '" + message + "' was sent to room '" + muc.getRoom() + "' by '" + muc.getNickname() + "'");
    } catch (InterruptedException | SmackException.NotConnectedException ex) {
        LOG.severe(ex.getLocalizedMessage());
    }
}

You can define a nickname when joining a chat room.

4.3.5 File transfers

To send/receive attachments, it is more complicated:

/**
 * File transfer.
 *
 * @param buddyJID recipient
 * @param res e.g. "Spark-2.8.3", default "Smack" (cannot be empty or null)
 * @param path path of the file attachment to send
 * @return {@code true} if file transfer was successful
 */
public boolean sendAttachment(String buddyJID, String res, String path) {
    LOG.info(String.format("Sending attachment '%1$s' to user %2$s", path, buddyJID));
    FileTransferManager fileTransferManager = FileTransferManager.getInstanceFor(connection);
    FileTransferNegotiator.IBB_ONLY = true;
    OutgoingFileTransfer fileTransfer = null;
    try {
        fileTransfer = fileTransferManager.createOutgoingFileTransfer(JidCreate.entityFullFrom(buddyJID + "/Spark-2.8.3"));
    } catch (XmppStringprepException ex) {
        LOG.log(Level.SEVERE, null, ex);
        return false;
    }
    if (fileTransfer != null) {
        OutgoingFileTransfer.setResponseTimeout(15 * 60 * 1000);
        LOG.info("status is:" + fileTransfer.getStatus());
        File file = Paths.get(path).toFile();
        if (file.exists()) {
            try {
                fileTransfer.sendFile(file, "sending attachment...");
            } catch (SmackException ex) {
                LOG.severe(ex.getLocalizedMessage());
                return false;
            }
            LOG.info("status is:" + fileTransfer.getStatus());
            if (hasError(fileTransfer)) {
                LOG.severe(getErrorMessage(fileTransfer));
                return false;
            } else {
                return monitorFileTransfer(fileTransfer, buddyJID);
            }
         } else                
            try {
                    throw new FileNotFoundException("File " + path + " not found!");
                } catch (FileNotFoundException ex) {
                    LOG.severe(ex.getLocalizedMessage());
                    return false;
                }
            }
        }
        return true;
}
 
/**
 * Monitor file transfer.
 *
 * @param fileTransfer
 * @param buddyJID
 * @return {@code false} if file transfer failed.
 */
private boolean monitorFileTransfer(FileTransfer fileTransfer, String buddyJID) {
    while (!fileTransfer.isDone()) {
            if (isRejected(fileTransfer) || isCancelled(fileTransfer)
                    || negotiationFailed(fileTransfer) || hasError(fileTransfer)) {
                LOG.severe("Could not send/receive the file to/from " + buddyJID + "." + fileTransfer.getError());
                LOG.severe(getErrorMessage(fileTransfer));
                return false;
            } else if (inProgress(fileTransfer)) {
                LOG.info("File transfer status: " + fileTransfer.getStatus() + ", progress: " + fileTransfer.getProgress());
            }
            try {
                Thread.sleep(1000);
            } catch (InterruptedException ex) {
                LOG.severe(ex.getLocalizedMessage());
            }
        }
        if (isComplete(fileTransfer)) {
            LOG.info(fileTransfer.getFileName() + " has been successfully transferred.");
            LOG.info("The file transfer is " + (fileTransfer.isDone() ? "done." : "not done."));
            return true;
        }
        return true;
}
 
public void receiveAttachment(String username) {
    final FileTransferManager manager = FileTransferManager.getInstanceFor(connection);
    manager.addFileTransferListener((FileTransferRequest request) -> {
        // Check to see if the request should be accepted
        if (request.getFileName() != null) {
            StringBuilder sb = new StringBuilder(BUFFER_SIZE);
            try {
                // Accept it
                IncomingFileTransfer transfer = request.accept();
                String filename = transfer.getFileName() + "_" + username;
                transfer.receiveFile(new File(filename));
                while (!transfer.isDone()) {
                    try {
                        Thread.sleep(1000);
                        LOG.info("STATUS: " + transfer.getStatus()
                                + " SIZE: " + sb.toString().length()
                                + " Stream ID : " + transfer.getStreamID());
                    } catch (Exception e) {
                        LOG.severe(e.getMessage());
                    }
                    if (transfer.getStatus().equals(FileTransfer.Status.error)) {
                        LOG.severe(transfer.getStatus().name());
                    }
                    if (transfer.getException() != null) {
                        LOG.severe(transfer.getException().getLocalizedMessage());
                    }
                }
                LOG.info("File received " + request.getFileName());
            } catch (SmackException | IOException ex) {
                LOG.severe(ex.getLocalizedMessage());
            }
        } else {
            try {
                // Reject it
                request.reject();
                LOG.warning("File rejected " + request.getFileName());
            } catch (SmackException.NotConnectedException | InterruptedException ex) {
                LOG.severe(ex.getLocalizedMessage());
            }
        }
    });
}

There are 3 types of file transfers defined in Openfire:

◉ in-band (FileTransferNegotiator.IBB_ONLY) where the message is broken down into chunks and sent as encoded messages. It is slower but works always. Additionally, it is easier to backup the messages exchanged as they are stored in the Openfire database.

◉ peer-to-peer (p2p) works great when both users are on the same network but fail when one user is behind a firewall or using NAT. It is faster and apart from the aforementioned problem you don’t have control of what is being exchanged.

◉ proxy server (SOCKS5 see XEP-0096 or the newer XEP-0234) uses a File Transfer Proxy but it requires opening port 7777. It is slower than p2p but faster than in-band.

In our test tool, in-band file transfer is being used.

Once you manage to send the file, you need to monitor its status (monitorFileTransfer()). There can be a network error or the recipient might simply refuse the file transfer. Actually, the other user has the option of accepting, rejecting, or ignoring the file transfer request.

While the sending of an attachment is an OutgoingFileTransfer, the reception is an IncomingFileTransfer. This is achieved by adding a listener to FileTransferManager. As already mentioned, the recipient needs to start listening before the sender sends the file. Additionally, in our load test the same file is being sent and received. In order to avoid overwriting the same file, the source file is stored under different name, adding "_" and the name of the recipient to the file name. Of course, these file names are written again and again while the load test tool is running.

4.4. Build

In order to be able to execute the load test tool, you need to create an executable XmppLoadTest-1.0.jar. One way is to add to your pom.xml the following:

<build>
    <plugins>
        <plugin>
            <groupId>org.apache.maven.plugins</groupId>
            <artifactId>maven-jar-plugin</artifactId>
            <version>3.2.0</version>
            <configuration>
                <archive>
                    <manifest>
                        <addClasspath>true</addClasspath>
                        <mainClass>test.xmpp.xmpploadtest.XmppLoadTest</mainClass>
                    </manifest>
                </archive>
            </configuration>
        </plugin>
    </plugins>
</build>

and you also need to include the dependencies to the classpath. Alternatively, you can use the dependency plugin to create a single jar that creates everything.

<plugin>
    <groupId>org.apache.maven.plugins</groupId>
    <artifactId>maven-assembly-plugin</artifactId>
    <version>3.2.0</version>
    <configuration>
        <archive>
            <manifest>
                <mainClass>test.xmpp.xmpploadtest.XmppLoadTest</mainClass>
            </manifest>
        </archive>
        <descriptorRefs>
            <descriptorRef>jar-with-dependencies</descriptorRef>
        </descriptorRefs>
    </configuration>
    <executions>
        <execution>
            <id>make-assembly</id> <!-- this is used for inheritance merges -->
            <phase>package</phase> <!-- bind to the packaging phase -->
            <goals>
                <goal>single</goal>
            </goals>
        </execution>
    </executions>
</plugin>

You may also use the maven command instead to execute it:

mvn exec:java -Dexec.mainClass=test.xmpp.xmpploadtest.XmppLoadTest "-Dexec.args=-s localhost -d localhost -p a -n 50"

4.5 Load test

Once you execute the load test tool, you will see a number of messages sent to the Openfire server. Depending on the scenario you chose (group chat or file transfers), if you connect as e.g. the 50th user using a chat client like Spark, and join the chat rooms, you will see them filled up by the same message sent again and again by the other 49 simulated users.

Apr 25, 2020 11:55:16 PM test.xmpp.xmpploadtest.XmppManager connect
INFO: Initializing connection to server localhost port 5222
Apr 25, 2020 11:55:18 PM test.xmpp.xmpploadtest.XmppManager connect
INFO: Connected: true
Apr 25, 2020 11:55:18 PM test.xmpp.xmpploadtest.XmppManager login
INFO: user001 authenticated? True
...
Apr 25, 2020 11:55:21 PM test.xmpp.xmpploadtest.XmppManager joinChatRoom
INFO: user001@localhost joined chat room room001@conference.localhost
Apr 25, 2020 11:55:21 PM test.xmpp.xmpploadtest.XmppManager joinChatRoom
INFO: user002@localhost joined chat room room002@conference.localhost
...
Apr 25, 2020 11:55:24 PM test.xmpp.xmpploadtest.XmppLoadTest chatRoomMessageTask
INFO: user001@localhost sent short message to room001@conference.localhost
Apr 25, 2020 11:55:25 PM test.xmpp.xmpploadtest.XmppLoadTest chatRoomMessageTask
INFO: user002@localhost sent short message to room002@conference.localhost
...

When you run the tool in the 2nd scenario, you don’t see any attachments in Spark or the chat client you connect to as user050.

INFO: Sending attachment 'test.txt' to user user003@localhost [Sun May 10 17:55:15 CEST 2020]
INFO: status is:Initial [Sun May 10 17:55:15 CEST 2020]
INFO: status is:Initial [Sun May 10 17:55:15 CEST 2020]
INFO: STATUS: Complete SIZE: 0 Stream ID : jsi_2604404248040129956 [Sun May 10 17:55:15 CEST 2020]
INFO: File received test.txt [Sun May 10 17:55:15 CEST 2020]
INFO: STATUS: In Progress SIZE: 0 Stream ID : jsi_4005559316676416776 [Sun May 10 17:55:16 CEST 2020]
WARNING: Closing input stream [Sun May 10 17:55:16 CEST 2020]
INFO: STATUS: In Progress SIZE: 0 Stream ID : jsi_6098909703710301467 [Sun May 10 17:55:16 CEST 2020]
INFO: STATUS: In Progress SIZE: 0 Stream ID : jsi_2348439600749627884 [Sun May 10 17:55:16 CEST 2020]
INFO: STATUS: In Progress SIZE: 0 Stream ID : jsi_8708250841661514027 [Sun May 10 17:55:16 CEST 2020]
INFO: STATUS: In Progress SIZE: 0 Stream ID : jsi_2119745768373873364 [Sun May 10 17:55:16 CEST 2020]
INFO: STATUS: In Progress SIZE: 0 Stream ID : jsi_6583436044582265363 [Sun May 10 17:55:16 CEST 2020]
INFO: STATUS: In Progress SIZE: 0 Stream ID : jsi_3738252107587424431 [Sun May 10 17:55:16 CEST 2020]
INFO: STATUS: In Progress SIZE: 0 Stream ID : jsi_4941117510857455094 [Sun May 10 17:55:16 CEST 2020]
INFO: test.txt has been successfully transferred. [Sun May 10 17:55:16 CEST 2020]
INFO: The file transfer is done. [Sun May 10 17:55:16 CEST 2020]