2021-12-12

68: Connection-Aware Possibly Remote Java External UNO Client

<The previous article in this series | The table of contents of this series | The next article in this series>

To incorporate the LibreOffice/OpenOffice functionality into your independent (non-macro) program, possibly a long-standing one like a Web application

Topics


About: UNO (Universal Network Objects)
About: LibreOffice
About: Apache OpenOffice
About: Java

The table of contents of this article


Starting Context



Target Context


  • The reader will know how to connect his or her external Java program to any UNO server (usually a LibreOffice or Apache OpenOffice instance), which can be in a remote computer, in a connection-aware way.

Orientation


There are an article on how to create a LibreOffice or Apache OpenOffice daemon and an article on how to create a LibreOffice or Apache OpenOffice Microsoft Windows service.

There is an article on optimally using LibreOffice or Apache OpenOffice as a files converter.

There is an article on executing any UNO dispatch command and getting all the information of the execution in Java.

There is an article on creating any UNO component in Java.

There are some articles on creating connection-aware external UNO clients in some other programming languages (C++, C#, and Python).


Main Body

Stage Direction
Here are Special-Student-7, Objector 9A, and Objector 9B in front of a computer.


1: The Clarification of the Goal


Special-Student-7
The goal of this article is to connect our independent Java program to any UNO server, in a scrupulous way.

"UNO server" is usually a LibreOffice or Apache OpenOffice instance, and "independent" means that the program is not any macro inside the LibreOffice or Apache OpenOffice instance.

Objector 9A
"a scrupulous way"? Is there also a dishonest way, then?

Special-Student-7
Sir, "scrupulous" here is more about meticulousness than about honesty.

Objector 9A
"meticulous"? Oh, come on, let's be relaxed, I'm an easy-going man!

Special-Student-7
. . . There is a lazy way, and that is the most pervasively promoted way, but that lazy way has some inconveniences.

Objector 9A
What inconveniences?

Special-Student-7
The lazy way insists that the UNO server should be in the local computer, gratuitously starts a LibreOffice or Apache OpenOffice instance in the local computer, and connects to the local LibreOffice or Apache OpenOffice instance, and does not detect if the connection is severed.

Objector 9A
Well, I don't like restrictions; restrictions are bad for easy-going.

Special-Student-7
Our program may be a long-standing one like a Web application or a GUI program, and it may be in an application server or in a client computer, while the UNO server may be in a remote data server, and the connection may be severed in a reason and the long-standing program will usually want to know if the connection is severed.

Objector 9A
The long-standing program would know the severance anyway when a procedure that used the connection failed.

Special-Student-7
But users would want to be informed before he or she inputted a lot of data on the screen, pushed a button, and found his or her effort futile, if the severance could be detected sooner.

Objector 9A
I want; futile effort is bad for easy-going.

Supposing that the program has detected the severance, can it reestablish a connection automatically?

Special-Student-7
It can try to reestablish a connection, but the cause of the problem has to be removed first of course, in order for the try to succeed.

For example, if the demise of the UNO server is the cause, a new UNO server may be able to be started automatically, but if someone has pulled a cable off, the cable will have to be replaced by hand.

Objector 9A
Supposing the program has automatically reconnected, can it go as though nothing happened?

Special-Student-7
That depends on how your program is implemented.

Objector 9A
How does it depend?

Special-Student-7
For example, if your program opens a document by Button A and writes something into the document by Button B, the program cannot continue from Button B if it has reconnected after Button A.

Objector 9A
It's not easy-going!

Special-Student-7
If your program opens a document and writes something into the document by a single Button C, it should be . . . easy-going.

Anyway, detecting the connection severances fast and doing it in an orderly way is a good thing.

Objector 9A
"an orderly way"?

Special-Student-7
Rather than having each module detect and deal with severances by failing, having a single events listener centrally detect and deal with severances is an orderly way for me.

Objector 9B
You keep saying "UNO server", but it is really nothing but a LibreOffice or Apache OpenOffice instance, right?

Special-Student-7
Madam, it is usually so, but "noting but" is not accurate.

Objector 9B
. . . Did I say a wrong thing? Oh, I made a irrevocable mistake!

Special-Student-7
. . . Madam, you did not say anything wrong; you just asked a question, right? You added "right?", right?

Objector 9B
Oh, yes, I'm relieved I have not made any mistake.

Special-Student-7
Well, a UNO server may be something you or someone have created.

Objector 9B
"may" . . ., but it is difficult, right?

Special-Student-7
It is rather easy to create, in fact.

Objector 9B
Oh, did I say something wrong?!

Special-Student-7
No, madam; you just asked a question.

Anyway, you are free to assume that "UNO server" is a LibreOffice or Apache OpenOffice instance, for your purpose, but please note that the LibreOffice or Apache OpenOffice instance has to be made a UNO server, in order to be a UNO server.

The UNO server can be in a remote computer, but in that case, the UNO server has to use the TCP/IP socket protocol.


2: Some Notes


Special-Student-7
It is supposed that a proper development environment is ready (there is an article for Linux or for Microsoft Windows).


3: The Code


Special-Student-7
Without further ado, this is the code.

theBiasPlanet/unoUtilitiesTests/connectingFromExternalProgramTest1/Test1Test.java

@Java Source Code
package theBiasPlanet.unoUtilitiesTests.connectingFromExternalProgramTest1;

import java.util.Scanner;
import com.sun.star.comp.helper.Bootstrap;
import com.sun.star.bridge.BridgeExistsException;
import com.sun.star.bridge.XBridge;
import com.sun.star.bridge.XBridgeFactory;
import com.sun.star.bridge.XInstanceProvider;
import com.sun.star.connection.NoConnectException;
import com.sun.star.connection.XConnection;
import com.sun.star.connection.XConnector;
import com.sun.star.frame.XDesktop;
import com.sun.star.lang.EventObject;
import com.sun.star.lang.XComponent;
import com.sun.star.lang.XEventListener;
import com.sun.star.lib.uno.helper.WeakBase;
import com.sun.star.uno.UnoRuntime;
import com.sun.star.uno.XComponentContext;
import com.sun.star.uno.XInterface;

public class Test1Test {
	public static class InitialUnoObjectsProvider extends WeakBase implements XInstanceProvider {
		private XComponentContext i_localUnoObjectsContext;
		
		public InitialUnoObjectsProvider (XComponentContext a_localUnoObjectsContext) {
			i_localUnoObjectsContext = a_localUnoObjectsContext;
		}
		
		@Override
		protected void finalize () {
		}
		
		@Override
		public XInterface getInstance (String a_initialUnoObjectName) {
			if ("theBiasPlanet.UnoObjectsContext" == a_initialUnoObjectName) {
				return i_localUnoObjectsContext;
			}
			else {
				return null;
			}
		}
	}
	
	public static class UnoConnectionEventsListener extends WeakBase implements XEventListener {
		public UnoConnectionEventsListener () {
		}
		
		@Override
		protected void finalize () {
		}
		
		@Override
		public void disposing (EventObject a_event) {
			System.out.println ("### The UNO connection has been severed.");
			System.out.flush ();
			synchronized (s_unoConnectionMaintainingThreadsCondition) {
				s_unoConnectionInformation.clear ();
				s_unoConnectionMaintainingThreadsCondition.notifyAll ();
			}
		}
	}
	
	public static class UnoConnectionInformation {
		private XBridge i_unoBridge;
		private XComponentContext i_remoteUnoObjectsContext;
		
		public UnoConnectionInformation () {
			i_unoBridge = null;
			i_remoteUnoObjectsContext = null;
		}
		
		@Override
		protected void finalize () {
		}
		
		public Boolean isEmpty () {
			return i_remoteUnoObjectsContext == null;
		}
		
		public XBridge getUnoBridge () {
			return i_unoBridge;
		}
		
		public XComponentContext getRemoteUnoObjectsContext () {
			return i_remoteUnoObjectsContext;
		}
		
		public void setUnoBridge (XBridge a_unoBridge) {
			i_unoBridge = a_unoBridge;
		}
		
		public void setRemoteUnoObjectsContext (XComponentContext a_remoteUnoObjectsContext) {
			i_remoteUnoObjectsContext = a_remoteUnoObjectsContext;
		}
		
		public void clear () {
			i_unoBridge = null;
			i_remoteUnoObjectsContext = null;
		}
	}
	
	static Object s_unoConnectionMaintainingThreadsCondition = new Object ();
	static Object s_unoConnectionUsingThreadsCondition = new Object ();
	static UnoConnectionInformation s_unoConnectionInformation = new UnoConnectionInformation ();
	
	public static void main (String [] a_argumentsArray) {
		int l_resultStatus = -1;
		try {
			if (a_argumentsArray.length != 1) {
				throw new Exception ("The arguments have to be these.\nThe argument 1: the server URL like 'socket,host=localhost,port=2002,tcpNoDelay=1;urp;StarOffice.ComponentContext'");
			}
			String l_unoServerUrl = a_argumentsArray [0];
			String [] l_unoServerUrlTokensArray = l_unoServerUrl.split (";");
			if (l_unoServerUrlTokensArray.length < 3) {
				throw new Exception ("The server URL has to have 3 tokens delimited by ';'.");
			}
			XComponentContext l_localUnoObjectsContext = Bootstrap.createInitialComponentContext (null);
			XBridgeFactory l_unoBridgesFactory = UnoRuntime.queryInterface (XBridgeFactory.class, l_localUnoObjectsContext.getServiceManager ().createInstanceWithContext ("com.sun.star.bridge.BridgeFactory", l_localUnoObjectsContext));
			InitialUnoObjectsProvider l_initialUnoObjectsProvider = new InitialUnoObjectsProvider (l_localUnoObjectsContext);
			UnoConnectionEventsListener l_unoConnectionEventsListener = new UnoConnectionEventsListener ();
			boolean [] l_ending = new boolean [] {false};
			Thread l_unoConnectingThread = new Thread ( () -> {
				boolean l_connectionIsEstablished = false;
				while (true) {
					l_connectionIsEstablished = false;
					if (l_ending [0]) {
						break;
					}
					synchronized (s_unoConnectionMaintainingThreadsCondition) {
						try {
							XConnection l_unoConnection = null;
							try {
								XConnector l_unoConnectionConnector = UnoRuntime.queryInterface (XConnector.class, l_localUnoObjectsContext.getServiceManager ().createInstanceWithContext ("com.sun.star.connection.Connector", l_localUnoObjectsContext));
								l_unoConnection = l_unoConnectionConnector.connect (l_unoServerUrlTokensArray [0]);
							}
							catch (NoConnectException l_exception) {
								throw new Exception (String.format ("%s: %s", "The UNO connection could not be established.", l_exception.toString ()));
							}
							try {
								s_unoConnectionInformation.setUnoBridge (l_unoBridgesFactory.createBridge ("", l_unoServerUrlTokensArray [1], l_unoConnection, l_initialUnoObjectsProvider));
							}
							catch (BridgeExistsException l_exception) {
								// This can't happen
							}
							s_unoConnectionInformation.setRemoteUnoObjectsContext ( UnoRuntime.queryInterface (XComponentContext.class, s_unoConnectionInformation.getUnoBridge ().getInstance (l_unoServerUrlTokensArray [2])));
							if (s_unoConnectionInformation.getRemoteUnoObjectsContext () == null) {
								s_unoConnectionInformation.clear ();
								throw new Exception ("The remote instance is not provided.");
							}
							UnoRuntime.queryInterface (XComponent.class, s_unoConnectionInformation.getUnoBridge ()).addEventListener (l_unoConnectionEventsListener);
							l_connectionIsEstablished = true;
							System.out.println ("### A UNO connection has been established.");
							System.out.flush ();
						}
						catch (Exception l_exception) {
							System.err.println (String.format ("### An error has occurred: \"%s\".", l_exception.toString ()));
							System.err.flush ();
						}
					}
					// This cannot be inside any 's_unoConnectionMaintainingThreadsCondition' lock, because it could cause a dead lock (because there is a reverse locks nest bellow), and this does not have to be inside the above lock, because although the connection might have been severed now, that is inevitable anyway, because the connection can be severed even in the above lock.
					if (l_connectionIsEstablished) {
						synchronized (s_unoConnectionUsingThreadsCondition) {
							s_unoConnectionUsingThreadsCondition.notifyAll ();
						}
					}
					synchronized (s_unoConnectionMaintainingThreadsCondition) {
						// This has to be checked, because the connection severance might have been detected and the waking call might have been already dispatched.
						if (! s_unoConnectionInformation.isEmpty ()) {
							// can safely wait, because the possible connection severance has not been detected.
							try {
								s_unoConnectionMaintainingThreadsCondition.wait ();
							}
							catch (InterruptedException l_exception) {
							}
							// Coming here means that the connection has been severed.
						}
					}
					try {
						Thread.sleep (3000);
					}
					catch (InterruptedException l_exception) {
					}
				}
			});
			l_unoConnectingThread.start ();
			// Do whatever you want in other daemon threads. Start
			Thread l_unoWorkingThread = new Thread ( () -> {
				boolean l_thereWasNoConnection = false;
				boolean l_errorHasOccurred = false;
				while (true) {
					try {
						l_thereWasNoConnection = false;
						l_errorHasOccurred = false;
						synchronized (s_unoConnectionMaintainingThreadsCondition) {
							if (! s_unoConnectionInformation.isEmpty ()) {
								System.out.println ("### Doing something.");
								System.out.flush ();
								XDesktop l_unoDesktop = UnoRuntime.queryInterface (XDesktop.class, s_unoConnectionInformation.getRemoteUnoObjectsContext ().getServiceManager ().createInstanceWithContext ("com.sun.star.frame.Desktop", s_unoConnectionInformation.getRemoteUnoObjectsContext ()));
								System.out.println ("### Doing something End.");
								System.out.flush ();
							}
							else {
								l_thereWasNoConnection = true;
								System.out.println (String.format ("%s", "### Warning: there is no UNO connection."));
								System.out.flush ();
							}
						}
					}
					catch (Exception l_exception) {
						l_errorHasOccurred = true;
						System.err.println (String.format ("### An error has occurred: \"%s\".", l_exception.toString ()));
						System.err.flush ();
					}
					// a new connection may have been established now (because 'Test1Test.s_unoConnectionMaintainingThreadsCondition' is not protected here), but there is no problem in checking.
					if (l_thereWasNoConnection || l_errorHasOccurred) {
						synchronized (s_unoConnectionUsingThreadsCondition) {
							// has to check that there is really no connection, not that just the connection severance is detected, because this thread has to wait in order for the severance to be detected.
							synchronized (s_unoConnectionMaintainingThreadsCondition) {
								if (s_unoConnectionInformation.isEmpty ()) {
									l_thereWasNoConnection = true;
								}
								else {
									try {
										s_unoConnectionInformation.getRemoteUnoObjectsContext ().getServiceManager ();
									}
									catch (Exception l_exception) {
										l_thereWasNoConnection = true;
									}
								}
							}
							if (l_thereWasNoConnection) {
								// a connection might have been established now (because 's_unoConnectionMaintainingThreadsCondition' is not protected here), but can safely wait, because anyway, the waking call is not dispatched yet at least (because 's_unoConnectionUsingThreadsCondition' has been protected since before the confirmation of non-connection).
								try {
									s_unoConnectionUsingThreadsCondition.wait ();
								}
								catch (InterruptedException l_exception) {
								}
							}
						}
					}
					try {
						Thread.sleep (3000);
					}
					catch (InterruptedException l_exception) {
					}
				}
			});
			l_unoWorkingThread.setDaemon (true);
			l_unoWorkingThread.start ();
			// Do whatever you want in other daemon threads. End
			System.out.println ("### Push 'Enter' to quit.");
			System.out.flush ();
			Scanner l_inputScanner = new Scanner (System.in);
			l_inputScanner.nextLine ();
			l_inputScanner.close ();
			synchronized (s_unoConnectionMaintainingThreadsCondition) {
				try {
					l_ending [0] = true;
					if (! s_unoConnectionInformation.isEmpty ()) {
						System.out.println ("### Severing the UNO connection.");
						System.out.flush ();
						UnoRuntime.queryInterface (XComponent.class, s_unoConnectionInformation.getUnoBridge ()).dispose ();
						s_unoConnectionInformation.clear ();
					}
					else {
						System.out.println ("### No UNO connection is established.");
						System.out.flush ();
					}
				}
				catch (Exception l_exception) {
					throw new Exception (String.format ("%s: %s.", "The UNO connection could not be severed", l_exception.toString ()));
				}
			}
			l_unoConnectingThread.join ();
			l_resultStatus = 0;
		}
		catch (Exception l_exception) {
			System.err.println (String.format ("### An error has occurred: \"%s\".", l_exception.toString ()));
			System.err.flush ();
		}
		System.exit (l_resultStatus);
	}
}

Objector 9A
. . . That is not easy-going, you know. It's long!

Special-Student-7
You know, the length of the code should really matter almost nothing for you; certainly, it mattered some for me, but you can just copy it if you will. Copying 1 line and copying 200 lines should not be much different as for your labor.

Objector 9A
That code does not look easy-going anyway.

Special-Student-7
That code is not something you have to write repeatedly; just 1 time copy for the program, which seems easy-going enough for me.


4: Compiling the Code


Special-Student-7
What you have to take care when you compile the code is that you include the UNO Jar files in the classes paths.

Objector 9B
What do you mean by "the UNO Jar files"?

Special-Student-7
As you can guess, you can use UNO in Java because there is a UNO library for Java; the UNO library consists of several Jar files, which are the UNO Jar files.

Objector 9B
Where are they?

Special-Student-7
They are in the 'program/classes' directory of the LibreOffice or Apache OpenOffice directory.

In fact, this will successfully compile the code.

@CMD Source Code
javac -g -deprecation -Xdiags:verbose -Xlint:unchecked -implicit:class -encoding UTF8 -classpath /usr/lib/libreoffice/program/classes/unoil.jar:/usr/lib/libreoffice/program/classes/jurt.jar:/usr/lib/libreoffice/program/classes/ridl.jar:/usr/lib/libreoffice/program/classes/juh.jar -d ~/myData/development/unoUtilitiesTests/intermediate/class ~/myData/development/unoUtilitiesTests/source/java/theBiasPlanet/unoUtilitiesTests/connectingFromExternalProgramTest1/Test1Test.java

Stage Direction
Special-Student-7 opens a terminal in the Linux machine in front of them, and executes the command on the terminal. The command ends successfully.


5: Executing the Code


Special-Student-7
There is a thing you have to do when you execute any Java external UNO client.

That is, of course, you have to include the UNO Jar files in the classes paths.

In fact, this should execute the code.

@bash or CMD Source Code
java -classpath ~/myData/development/unoUtilitiesTests/intermediate/class:/usr/lib/libreoffice/program/classes/unoil.jar:/usr/lib/libreoffice/program/classes/jurt.jar:/usr/lib/libreoffice/program/classes/ridl.jar:/usr/lib/libreoffice/program/classes/juh.jar theBiasPlanet.unoUtilitiesTests.connectingFromExternalProgramTest1.Test1Test '-Pi_commandLineArguments=socket,host=localhost,port=2002,tcpNoDelay=1;urp;StarOffice.ComponentContext'

"socket,host=localhost,port=2002,tcpNoDelay=1;urp;StarOffice.ComponentContext" is the address of the UNO server, to be passed as the argument for the program (of course, change the host and the port for your environment).

Stage Direction
Special-Student-7 executes the command on the afore-opened terminal. "### The UNO connection could not be established." keeps coming every 3 seconds.

Objector 9B
You made some mistake, I'm sure.

Special-Student-7
It was not a mistake than I intentionally have not started up any UNO server in order to see how the program does when it cannot establish any connection.

It is doing well in fact.

Now, let met start up a UNO server.

Stage Direction
Special-Student-7 starts up a LibreOffice instance as a UNO server. "### A UNO connection has been established." appears on the console, and then "### Doing something Start." and "### Doing something End." start coming every 3 seconds.

Objector 9B
It looks doing well.

Special-Student-7
It is.

Now, let me kill the UNO server.

Stage Direction
Special-Student-7 kill the LibreOffice instance. "### The UNO connection has been severed." and "### Warning: there is no UNO connection." appear, and then "### The UNO connection could not be established." starts coming again.

Special-Student-7
Note that I have killed the instance because closing the LibreOffice windows GUI-wise may not really stop the instance

Now, let me restart a UNO server.

Stage Direction
Special-Student-7 starts up a LibreOffice instance. "### A UNO connection has been established." appears on the console, and then "### Doing something Start." and "### Doing something End." start coming every 3 seconds.

Objector 9B
Hmm, that is what you mean by "connection-aware".

Special-Student-7
Pushing the 'Enter' key on the console will stop the program.

Stage Direction
Special-Student-7 push the 'Enter' key, and the program stops.


6: Understanding the Code


Special-Student-7
Let us understand the code; I said you could copy the code but I did not say you did not need to understand the code.

Objector 9A
Huh? "understand" that lengthy code? It's not easy-going!

Special-Student-7
I do not coerce you if you do not want to, but sometimes, what is not easy-going is beneficial.

Objector 9A
Not easy-going, but beneficial? I don't understand that concept.

Special-Student-7
There are 3 standing threads: the main thread, the connection-establishing thread ("l_unoConnectingThread"), and the working thread ("l_unoWorkingThread"), and 1 sporadically evoked thread: the connection-severance-detecting thread.

1st of all, the program has to do bootstrapping, which means that the program becomes a UNO program.

Note that the program has to become a UNO program in order to connect to a UNO server, because the program connects to the UNO server by means of UNO.

Specifically, "Bootstrap.createInitialComponentContext (null)" does the bootstrapping and achieves a local UNO objects context.

"local" means that it is of the program, not of the UNO server.

The local UNO environment has its own UNO services manager, which can instantiate some UNO services in the local UNO environment.

Of course, the local UNO environment does not have any UNO component of like spread sheets documents, but has some UNO components that are necessary in order to connect the local UNO environment to any UNO server, and the program instantiates those UNO components via the local UNO services manager.

In fact, the program instantiates the "com.sun.star.connection.Connector" UNO service.

Then, the program calls the "connect" method of the "com.sun.star.connection.Connector" instance and achieve a "com.sun.star.connection.XConnection" instance.

The method name is "connect", but the program has to establish a bridge to the UNO server, in order to really be connected.

Meanwhile, the program prepares an initial UNO objects provider.

Objector 9B
"initial UNO objects provider"? Is there also a second provider, then?

Special-Student-7
It is 'initial UNO objects' than 'initial provider'

The UNO server can achieve some UNO objects from the local program when the bridge is established via the initial UNO objects provider; those UNO objects are initial UNO objects, because they are the 1st UNO objects accessed by the UNO server.

Objector 9B
"some UNO objects"? What UNO objects, exactly?

Special-Student-7
They are whatever UNO objects the initial UNO objects provider determines to offer, but usually it is the local UNO objects context, because it is usually via the local UNO objects context that the local UNO environment is handled.

Objector 9B
"handled"? Will my program be handled by the LibreOffice instance?

Special-Student-7
In fact, no. There is that mechanism, but the LibreOffice instance does not use it, so the initial UNO objects provider is not really meaningful if the UNO server is a LibreOffice or Apache OpenOffice instance.

Objector 9B
So? Do I still have to prepare it?

Special-Student-7
No. You can just pass 'null' where it is required.

Objector 9B
I will do so.

Special-Student-7
Then, the program instantiates the "com.sun.star.bridge.BridgeFactory" UNO service.

Then, the program creates a UNO bridge by calling the "createBridge" method of the "com.sun.star.bridge.BridgeFactory" instance.

Then, the program achieves a remote UNO objects context by the "getInstance" method of the "com.sun.star.bridge.XBridge" instance.

You know, that remote UNO objects context is the access point to the UNO server, so you will want to keep it.

For your information, the remote UNO objects context can be gotten from the UNO server because the UNO server offers it via its own initial UNO objects provider.

Objector 9B
Ah, so, it is an initial UNO object.

Special-Student-7
The program does one more thing: registering an events listener into the bridge.

The evens listener has to have implemented the "com.sun.star.lang.XEventListener" UNO interface, which has the "disposing" method, which is called when the connection is severed.

The "addEventListener" method registers the events listener into the UNO bridge.

Objector 9B
The connection is ready at last.

Special-Student-7
Some words on the multiple threads structure of the program.

The "l_unoConnectingThread" thread is active while there is no connection and tries to establish a connection; when the connection is established, the thread begins to wait indefinitely.

The events listener (which runs in an evoked thread) notifies the "l_unoConnectingThread" thread when the connection is severed, and the "l_unoConnectingThread" thread becomes active again, trying to establish a new connection.

The "l_unoWorkingThread" thread is active while there is a connection and does something; when the connection is severed, the thread begins to wait indefinitely.

Imagine that the working thread is dealing with user requests of a Web application; if a user is unlucky, the user might experience an error, but otherwise, the user would be warned before the user did some onerous labor futilely that the Web application was temporarily inoperable (the "### Warning: there is no UNO connection." message represents that situation).

Objector 9B
I see. That's reasonable, but I can't make head or tail of those lengthy comments in the code.

Special-Student-7
While the basic concept is simple, the actual coordination of multiple threads is rather delicate, as usually is.

There are some points to be attended.

1st, any dead lock has to be avoided.

In the "l_unoConnectingThread" thread, there is a seemingly tortuous procedure of "s_unoConnectionMaintainingThreadsCondition"'s being locked and then released, and then "s_unoConnectionUsingThreadsCondition"'s being locked and then released, and then "s_unoConnectionMaintainingThreadsCondition"'s being locked and then released, but that is because "s_unoConnectionUsingThreadsCondition" cannot be locked while "s_unoConnectionMaintainingThreadsCondition" is being locked, because that could cause a dead lock.

Objector 9B
Why?

Special-Student-7
Because the "l_unoWorkingThread" thread has to do lockings in the reverse order, and 2 threads' doing lockings in the reverse orders could cause a dead lock, because the thread 1 could lock an object A; the thread 2 could lock an object B; the thread 1 wanted to lock the object B but could not, because the object B was already locked by the thread 2 while the thread 2 wanted to lock the object A but could not, because the object A was already locked by the thread 1, a dead lock.

Objector 9B
Why does "l_unoWorkingThread" have to do lockings in the reverse order?

Special-Student-7
That is because of the 2nd point to be attended.

Any thread can wait only when it is guaranteed to be properly awaken.

That is because otherwise, the thread could be keep waiting indefinitely.

Objector 9B
I understand, so?

Special-Student-7
In order for the thread to wait on "s_unoConnectionUsingThreadsCondition", "s_unoConnectionUsingThreadsCondition" has to have been locked, but also "s_unoConnectionMaintainingThreadsCondition" has to be locked in order for "s_unoConnectionInformation" to be checked (because "s_unoConnectionMaintainingThreadsCondition" is protecting "s_unoConnectionInformation").

Objector 9B
So? Can't the thread lock "s_unoConnectionMaintainingThreadsCondition" first and then lock "s_unoConnectionUsingThreadsCondition"?

Special-Student-7
No, because that would have "s_unoConnectionMaintainingThreadsCondition" keep locked while the thread is waiting.

Objector 9B
So?

Special-Student-7
If so, "l_unoConnectingThread" would keep locked out, without being able to establish a new connection; so, the program would be stuck for ever.

Objector 9B
Of course . . .. Can't the thread check "s_unoConnectionInformation" and release "s_unoConnectionMaintainingThreadsCondition", and then lock "s_unoConnectionUsingThreadsCondition"?

Special-Student-7
No, because "s_unoConnectionInformation" might be changed after "s_unoConnectionMaintainingThreadsCondition" is released and before "s_unoConnectionUsingThreadsCondition" is locked, then, the checking would be unreliable.

Objector 9B
I see, but "s_unoConnectionMaintainingThreadsCondition" has been released before the thread starts waiting in your code, which seems to mean that a connection might have been already established before the thread starts waiting, according to your theory.

Special-Student-7
It might, but that is really OK, because as "s_unoConnectionUsingThreadsCondition" is locked, the awaking call cannot have been dispatched yet anyway, which is the point. I mean, a connection might have been already established certainly, but the thread is guaranteed to receive the awaking call nevertheless.

Objector 9B
Well, . . . it seems so.

Special-Student-7
As for the "l_unoConnectingThread" thread, when it starts waiting, the connection might have been already severed, but the severance is guaranteed to have not been detected yet, so, the awaking call is guaranteed to have not been dispatched yet.

Objector 9B
That is because "s_unoConnectionMaintainingThreadsCondition" is locked by the "l_unoConnectingThread" thread and the connection-severance-detecting thread has to lock "s_unoConnectionMaintainingThreadsCondition" before it detects the severance.

Ah-ha, the point is that any thread has to be sure that the expected awaking call has not been already dispatched before the thread starts waiting.

Special-Student-7
Yes, because otherwise, what the waiting thread would not be woken up for ever.

And the 3rd point to be attended is that any thread has to wait when another thread must run.

Objector 9B
Huh? I don't think so. It's a multi-threading, right? Multiple threads can run at the same time, right?

Special-Student-7
Not necessarily, depending on the run-time environment. Multiple threads will not really run simultaneously if there is only 1 CPU only 1 core, so, the another thread may not be given any time slice while the running thread keeps running unless it waits.

Objector 9B
Oh, I said a wrong thing! It's an irrevocable mistake! I'm done!

Special-Student-7
No, no, madam, you are fine; you just didn't imagine such a poor environment.

Objector 9B
Oh? Yes, it's too poor! Single core? It doesn't concern my world!

Special-Student-7
Anyway, "l_unoWorkingThread" has to wait in order for the connection-severance-detecting thread to be guaranteed to run.

Objector 9B
But as "l_unoWorkingThread" sleeps periodically, the connection-severance-detecting thread would be given a chance while "l_unoWorkingThread" is sleeping.

Special-Student-7
I guess so, but please suppose that "sleep" is not there: "sleep" is there just because incessant message outputs are a nuisance.

Objector 9B
What if the program was a Web server?

Special-Student-7
If the program was a Web server, it would not be a loop, and the connection-severance-detecting thread would be evoked between user requests.


7: A Workable Sample


Special-Student-7
Here is a workable sample, which automatically makes a save file of whatever file stored locally by the LibreOffice or Apache OpenOffice instance (note that the LibreOffice or Apache OpenOffice instance is supposed to be local in this case).

The main project is 'connectionAwareExternalUnoClients' and how to build the project is described in a previous article.

The program can be executed like this.

@bash or CMD Source Code
gradle i_executeJarTask -Pi_commandLineArguments="socket,host=localhost,port=2002,tcpNoDelay=1;urp;StarOffice.ComponentContext"

Note that its code is not written as flatly as the above code, but what is being done is the same, stashed in my utility classes like 'theBiasPlanet.unoUtilities.processesHandling.UnoProcessEnvironment.UnoProcessEnvironment', 'theBiasPlanet.unoUtilities.connectionsHandling.UnoConnectionConnector.UnoConnectionConnector', and 'theBiasPlanet.unoUtilities.connection.UnoConnection.UnoConnection'.

Objector 9A
What is "flatly"?

Special-Student-7
The above code has been written only for the explanation's sake; I usually do not write that way; I usually more appropriately organize things in classes.

Objector 9A
Is that easy-going?

Special-Student-7
For example, that chunk of connection-establishing procedure is a boilerplate, which I am not grad to flatly write in each UNO external client, so I confine it in a utility class; UNO has 'com.sun.star.connection.XConnection' and 'com.sun.star.bridge.XBridge' objects, but that distinction is not really helpful for me, so I have a single class that wholly represents a UNO connection; especially, as UNO is somewhat cumbersome in that we have to take out various UNO proxies of the same UNO object, I have a utility class ('theBiasPlanet.unoUtilities.pointers.UnoObjectPointer') that corresponds to the UNO component, not to each UNO interface, and have the utility class handle such various UNO proxies.

Objector 9A
Is that easy-going?

Special-Student-7
. . . It is, for me.


References


<The previous article in this series | The table of contents of this series | The next article in this series>