2022年6月19日日曜日

68: コネクションアウェアなリモートにも置けるJava外部UNOクライアント

<このシリーズの前の記事 | このシリーズの目次 | このシリーズの次の記事>

LibreOffice/OpenOfficeの機能をあなたの自立した(非マクロ)プログラム(Webアプリケーションのような長時間動作するものでもよい)に組み込むために

話題


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

この記事の目次


開始コンテキスト



ターゲットコンテキスト



  • 読者は、自分の外部Javaプログラムを、リモートコンピュータにあってもよい任意のUNOサーバー(通常は、LibreOfficeまたはApache OpenOfficeインスタンス)へ、コネクションアウェアに接続する方法を知る。

オリエンテーション


LibreOfficeまたはApache OpenOfficeデーモンを作成する方法の記事およびLibreOfficeまたはApache OpenOffice Microsoft Windowsサービスを作成する方法の記事があります。

LibreOfficeまたはApache OpenOfficeをファイルコンバーターとして最適に利用することについての記事があります。

Javaにて任意のUNOディスパッチコマンドを実行して実行の全情報を取得することについての記事があります。

Javaにて任意のUNOコンポーネントを作成することについての記事があります。

コネクションアウェアな外部UNOクライアントを他のプログラミング言語たちで作成することについての記事があります(C++C#Python)。


本体

ト書き
Special-Student-7、Objector 9A、Objector 9Bがコンピュータの前にいる。


1: 目標の明確化


Special-Student-7
本記事の目標は、自分の自立Javaプログラムを任意のUNOサーバーに、scrupulous(良心的な、用意周到な)に接続することです。

"UNOサーバー"は通常LibreOfficeまたはApache OpenOfficeインスタンスであり、"自立"は、プログラムがLibreOfficeまたはApache OpenOfficeインスタンス内マクロでないことを意味します。

Objector 9A
"良心的な方法"?不正直な方法なんてのもあるのか、それじゃあ?

Special-Student-7
サー、ここでの"scrupulous"は、正直さに関してというより周到さに関してです。

Objector 9A
"周到"?おいおい、リラックスしようぜ。俺はお気楽な人間なんだぜ!

Special-Student-7
. . . 怠惰な方法があり、それが最も広く宣伝されている方法ですが、その怠惰な方法にはいくつか不都合があります。

Objector 9A
どんな不都合だ?

Special-Student-7
怠惰な方法は、UNOサーバーはローカルコンピューター内にいなければならないと主張し、頼みもしないのに、ローカルコンピューターにLibreOfficeまたはApache OpenOfficeインスタンスを開始し、そのローカルLibreOfficeまたはApache OpenOfficeインスタンスへ接続し、コネクションが切断されても検出しません。

Objector 9A
まあ、俺は制限を好まないがな。制限はお気楽には良くない。

Special-Student-7
私たちのプログラムは、WebアプリケーションやGUIプログラムのような長時間動作するものかもしれず、アプリケーションサーバーかクライアントコンピューター内にいるかもしれず、その一方で、UNOサーバーはリモートデータサーバー内にいるかもしれず、コネクションはある理由によって切断されるかもしれず、その長時間動作するプログラムは通常、もしもコネクションが切断されたら知りたいでしょう。

Objector 9A
その長時間動作するプログラムはどのみち切断を知るだろう、その切断を使った処理が失敗したときに。

Special-Student-7
でも、ユーザーたちは、画面上で大量のデータを入力し、自分の努力が無駄であったことを発見する前に知らせて欲しいでしょう、もしも、切断がもっと早く検出できたのであれば。

Objector 9A
俺は、してほしいよ。無駄な努力はお気楽には悪いからな。

プログラムは、切断を検出したとして、コネクションを自動的に再確立できるのか?

Special-Student-7
コネクションを再確立するよう試みることができますが、勿論、問題の原因がまず取り除かれる必要があります、試みが成功するためには。

例えば、もしも、UNOサーバーの死が原因であったならば、新たなUNOサーバーを自動的に開始できるかもしれませんが、もしも、誰かがケーブルを1本抜いたのであれば、そのケーブルは手で戻されなければいけないでしょう。

Objector 9A
プログラムは、自動的に再接続したとして、何事もなかったかのように動作できるのか?

Special-Student-7
それは、あなたのプログラムがどのように実装されているかによります。 

Objector 9A
どのようによるんだ?

Special-Student-7
例えば、あなたのプログラムは、ボタンAによってあるドキュメントをオープンし、ボタンBによってそのドキュメントに何か書くとしたら、ボタンAの後に再接続したとしたら、ボタンBから続けることはできません。

Objector 9A
それはお気楽じゃない!

Special-Student-7
もしも、あなたのプログラムはあるドキュメントをオープンしそのドキュメントに何かを書くというのを単一のボタンCによって行なうとしたら、それは、 . . . お気楽でしょう。

いずれにせよ、コネクション切断を速やかに検出すること、そしてそれを整然とした方法で行なうことは、良いことです。

Objector 9A
"整然とした方法"?

Special-Student-7
各モジュールに、失敗することによって、切断を検出・対処させるのではなく、単一のイベントハンドラに、集中管理で切断を検出・対処させるのが、私にとっての整然とした方法です。

Objector 9B
あなたはずっと"UNOサーバー"と言ってるけど、それって、実際には、LibreOfficeまたはApache OpenOfficeインスタンス以外の何物でもないんでしょう?

Special-Student-7
マダム、大抵はそうですが、"以外の何物でもない"は正確ではありません。

Objector 9B
. . . 私は間違ったことを言ったの?ああ、私は取り返しのつかない間違いを犯したんだわ!

Special-Student-7
. . . マダム、あなたは何も間違ったことをおっしゃっていません。ただ質問をされただけでしょう?"でしょう?"を付けられましよね?

Objector 9B
ああ、はい、何も間違いを犯してなくて安心した。

Special-Student-7
えーと、UNOサーバーは、あなたもしくはどなたかが作成したものかもしれません。

Objector 9B
"かもしれない" . . .、でも、難しいでしょう?

Special-Student-7
作成するのはかなり容易です、実のところ。

Objector 9B
ああ、私、間違ったことを言った?!

Special-Student-7
いいえ、マダム、あなたは質問をされただけです。

それはとにかく、"UNOサーバー"はLibreOfficeまたはApache OpenOfficeインスタンスであると想定するのはご自由です、あなたの目的のためには、しかし、LibreOfficeまたはApache OpenOfficeインスタンスはUNOサーバーにされなければならないということにご留意ください、UNOサーバーであるためには。

UNOサーバーは、リモートコンピューター内にいてもよいですが、その場合は、そのUNOサーバーはTCP/IPソケットプロトコルを使用しなければなりません。


2: 若干の注意


Special-Student-7
適切な開発環境が用意済みであると想定されています(ある記事があります、Linux用またはMicrosoft Windows用に)。


3: コード


Special-Student-7
あれこれ言うのはこれぐらいにして、以下がコードです。

theBiasPlanet/unoUtilitiesTests/connectingFromExternalProgramTest1/Test1Test.java

@Java ソースコード
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
. . . それはお気楽じゃないな、おい、長いぞ!

Special-Student-7
お分かりでしょうが、コードの長さはあなたにとって実際にはほとんど何でもないはずです。確かに、私には何でもなくありませんでしたが、あなたはただそれをコピーすればよいだけです、もしもよろしければ。1行をコピーするのも200行をコピーするのも、あなたの労力にとってたいして違いはないはずです。

Objector 9A
そのコードはお気楽には見えないぞ、どのみち。

Special-Student-7
そのコードは、あなたが繰り返し書かなければならないというものではありません。プログラムに対してただ1回コピーする、私には十分お気楽に思えますが。


4: コードをコンパイルする


Special-Student-7
そのコードをコンパイルする際に手当てしなければならないことは、UNO Jarファイルたちをクラスパスに含めることです。

Objector 9B
"UNO Jarファイルたち"ってどういう意味?

Special-Student-7
ご推測できるとおり、あなたがJava内でUNOを使えるのは、Java用のUNOライブラリがあるからであり、そのUNOライブラリはいくつかのJarファイルから成っており、それらがUNO Jarファイルたちです。

Objector 9B
それらはどこにあるの?

Special-Student-7
それらは、LibreOfficeまたはApache OpenOfficeディレクトリの'program/classes'ディレクトリ内にあります。

実のところ、以下が、当該コードをコンパイル成功させるでしょう。

@CMD ソースコード
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

ト書き
Special-Student-7は、彼らの前のLinuxマシンにターミナルを1つ開き、そのターミナル上でそのコマンドを実行する。 コマンドは成功で終わる。


5: コードを実行する


Special-Student-7
任意のJava外部UNOクライアントを実行する際にあなたが行わなければならないことが1つあります。

それは、勿論、例のUNO Jarファイルたちをクラスパスに含めないといけないということです。

実のところ、以下が、当該コードを実行するはずです。

@bash or CMD ソースコード
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"は、UNOサーバーのアドレスであり、プログラムの引数として渡されます(勿論、あなたの環境に合わせてホストおよびポートを変更してください)。

ト書き
Special-Student-7は、コマンドを、先程言及されたターミナル上で実行する。 "### The UNO connection could not be established."が3秒おきに現わ続ける。

Objector 9B
あなたは何か間違いを犯したのね。

Special-Student-7
間違いというより、意図的に、UNOサーバーを開始せず、プログラムがコネクションを確立できない時にどう振る舞うかを見ようとしたのです。

うまくやっています、実際のところ。

それでは、UNOサーバーを開始させてください。

ト書き
Special-Student-7は、LibreOfficeインスタンスをUNOサーバーとして起動する。 "### A UNO connection has been established."がコンソール上に現われる、次に、"### Doing something Start."および"### Doing something End."が3秒おきに現われ始める。

Objector 9B
うまくやってるようね。

Special-Student-7
やっています。

それでは、UNOサーバーを殺させてください。

ト書き
Special-Student-7は、LibreOfficeインスタンスを殺す。 "### The UNO connection has been severed."および"### Warning: there is no UNO connection."が現われ、次いで、"### The UNO connection could not be established."が再び現れ始める。

Special-Student-7
注意として、私はインスタンスを殺しましたが、それは、LibreOfficeウィンドウたちをGUI的にクローズしても、本当にはインスタンスをストップしないかもしれないからです。

それでは、UNOサーバーを再び起動させてください。

ト書き
Special-Student-7は、LibreOfficeインスタンスを起動する。 "### A UNO connection has been established."がコンソール上に現われ、次いで、"### Doing something Start."および"### Doing something End."が3秒おきに現われ始める。

Objector 9B
ふーむ、それが、あなたが"コネクションアウェア"で意味していることね。

Special-Student-7
コンソール上で'Enter'キーを押せば、プログラムがストップします。

ト書き
Special-Student-7は、'Enter'キーを押し、プログラムは停止する。


6: コードを理解する


Special-Student-7
コードを理解しましょう。あなたはコードをコピーできると申しましたが、あなたはコードを理解する必要はないとは申しませんでした。

Objector 9A
はあ?あの長々しいコードを"理解する"?それはお気楽じゃないぞ!

Special-Student-7
したくなければ強要はしませんが、時には、お気楽でないことが有益です。

Objector 9A
お気楽でないが、有益?その概念は理解できんな。

Special-Student-7
3つの常時スレッド: メインスレッド、コネクション確立スレッド、("l_unoConnectingThread")、ワーキングスレッド("l_unoWorkingThread")、および1つの散発的に起動されるスレッド: コネクション切断検出スレッドがあります。

まず最初に、プログラムはブートストラッピング(プログラムがUNOプログラムになることを意味します)を行わなければなりません。

ご注意いただきたいのですが、プログラムは、UNOサーバーに接続するには、UNOプログラムにならなければなりません、なぜなら、プログラムはUNOサーバーにUNOを手段として接続するからです。

具体的には、" 具体的には、"bootstrap.createinitialcomponentcontext (null)"がブートストラッピングを行ない、ローカルUNOオブジェクト群コンテキストを獲得します。 "がブートストラッピングを行ない、ローカルUNOオブジェクト群コンテキストを獲得します。

"ローカル"は、それが、そのプログラムのものであり、UNOサーバーのものではないことを意味します。

ローカルUNO環境はそれ自身のUNOサービス群マネージャーを持っており、そのUNOサービス群マネージャーはいくつかのUNOサービスをローカルUNO環境内にインスタンス化できます。

勿論、ローカルUNO環境には、スプレッドシートドキュメントのようなUNOコンポーネントはないが、ローカルUNO環境をUNOサーバーに接続するために必要ないくつかのUNOコンポーネントがあり、プログラムは、それらのUNOコンポーネントをローカルUNOサービス群マネージャーを介してインスタンス化します。 

実際、プログラムは"com.sun.star.connection.Connector"UNOサービスをインスタンス化します。

次に、プログラムは"com.sun.star.connection.Connector"インスタンスの"connect"メソッドをコールして、"com.sun.star.connection.XConnection"インスタンスを獲得します。

そのメソッド名は"connect"ですが、プログラムはUNOサーバーへのブリッジを確立しなければなりません、本当に接続するためには。

その一方で、プログラムはイニシャルUNOオブジェクト群プロバイダを用意します。

Objector 9B
"イニシャルUNOオブジェクト群プロバイダ"?じゃあ、セカンドプロバイダもあるの?

Special-Student-7
'イニシャルUNOオブジェクト群'です、'イニシャルプロバイダ'ではなく。

UNOサーバーはローカルプログラムからいくつかのUNOオブジェクトたち を獲得できます、ブリッジが確立されたときにイニシャルUNOオブジェクト群プロバイダを介して。それらのUNOオブジェクトたちがイニシャルUNOオブジェクトたちです、というのは、それらが、UNOサーバーからアクセスされる最初のUNOオブジェクトたちだからです。

Objector 9B
"いくつかのUNOオブジェクト"?どのUNOオブジェクトよ、具体的には?

Special-Student-7
それらは、イニシャルUNOオブジェクト群プロバイダがオファーすると決めた何でもよいUNOオブジェクトたちですが、大抵は、ローカルUNOオブジェクト群コンテキストで、それは、大抵、ローカルUNO環境が操作されるのはローカルUNOオブジェクト群コンテキストを介してだからです。

Objector 9B
"操作される"?私のプログラムはLibreOfficeインスタンスに操作されるの?

Special-Student-7
実のところ、いいえ。そのメカニズムはありますが、LibreOfficeインスタンスはそれを使いません、したがって、UNOサーバーがLibreOfficeまたはApache OpenOfficeインスタンスであれば、イニシャルUNOオブジェクト群プロバイダは本当に意味深いというわけではありません。

Objector 9B
それで?それでも私はそれを準備しなければならないわけ?

Special-Student-7
いいえ。それが要求されるところにあなたは'null'を渡すことができます。

Objector 9B
そうするわ。

Special-Student-7
次に、プログラムは"com.sun.star.bridge.BridgeFactory"UNOサービスをインスタンス化します。

次に、プログラムはUNOブリッジを生成します、"com.sun.star.bridge.BridgeFactory"インスタンスの"createBridge"メソッドをコールすることによって。

次に、プログラムは、リモートUNOオブジェクト群コンテキストを獲得します、"com.sun.star.bridge.XBridge"インスタンスの"getInstance"によって。

ご存知でしょうが、そのリモートUNOオブジェクト群コンテキストがUNOサーバーへのアクセスポイントになるので、それをキープされたいでしょう。

情報として申し上げると、そのリモートUNOオブジェクト群コンテキストがUNOサーバーから取得できるのは、UNOサーバーがそれを自身のイニシャルUNOオブジェクト群プロバイダを介してオファーしているからです。

Objector 9B
ああ、それじゃあ、それはイニシャルUNOオブジェクトなのね。

Special-Student-7
プログラムはもう1つのことを行ないます: イベントリスナーをブリッジに登録します。

そのイベントリスナーは"com.sun.star.lang.XEventListener"UNOインターフェイス("disposing"メソッドを持っており、そのメソッドはコネクションが切断されたときに呼ばれる)を実装しておかなければなりません。

"addEventListener"メソッドはそのイベントリスナーをそのUNOブリッジへ登録します。

Objector 9B
コネクションが準備できたわけね、ようやく。

Special-Student-7
当プログラムのマルチスレッド構造についていくらか言っておきましょう。

"l_unoConnectingThread"スレッドはコネクションがない間はアクティブであって、コネクションを確立しようとします。コネクションが確立されると、そのスレッドは無期限に待ち始めます。

イベントリスナー(呼び起こされるスレッドで走る)は、コネクションが切断されると"l_unoConnectingThread"スレッドに通知し、"l_unoConnectingThread"スレッドは再びアクティブになり、新たなコネクションを確立しようと試みます。

"l_unoWorkingThread"スレッドは、コネクションがある間はアクティブであり、何かを行ないます。コネクションが切断されると、スレッドは無期限に待ち始めます。

ワーキングスレッドはWebアプリケーションのユーザーリクエスト群に対処しているものと想像してください。もしも、ユーザーが不運だったら、ユーザーはエラーを経験するかもしれませんが、そうでなければ、ユーザーは、労苦を無益に行なう前に、Webアプリケーションは一時的に処理不能状態にあると警告されるでしょう(前出の"### Warning: there is no UNO connection."メッセージはその状況を表わしています)。

Objector 9B
なるほど。それはリーズナブルだけど、コード中のあれらの長々しいコメントがなんのことやら分からない。

Special-Student-7
基本的コンセプトはシンプルですが、複数スレッドたちの実際の調整はかなりデリケートです、通常そうであるとおり。

手当てをしなければならないポイントがいくつかあります。

第1に、デッドロックは避けなければなりません。

"l_unoConnectingThread"スレッド内に、"s_unoConnectionMaintainingThreadsCondition"がロックされて次にリリースされ、次に"s_unoConnectionUsingThreadsCondition"がロックされて次にリリースされ、そして次に"s_unoConnectionMaintainingThreadsCondition"がロックされて次にリリースされるという、曲折したように思われる処置がありますが、その理由は、"s_unoConnectionUsingThreadsCondition"は"s_unoConnectionMaintainingThreadsCondition"がロックされている最中にロックできないことです、それはデッドロックを起こす可能性がありますから。

Objector 9B
なんで?

Special-Student-7
なぜなら、"l_unoWorkingThread"スレッドは、逆の順序でロックを行わなければならず、2つのスレッドが互いに逆順にロックを行なうと、デッドロックを起こす可能性があります、なぜなら、スレッド1がオブジェクトAをロックし、スレッド2がオブジェクトBをロックし、スレッド1はオブジェクトBをロックしたいが、できない、オブジェクトBは既にスレッドによってロックされているから、その一方でスレッド2はオブジェクトAをロックしたいが、できない、オブジェクトAはすでにスレッド1によってすでにロックされているいるから、デッドロックです。

Objector 9B
なんで、"l_unoWorkingThread"はロックを逆順に行わなければならないの?

Special-Student-7
それは、手当てをしなければならない第2点のためです。

任意のスレッドは、それが適切に起こされると保証されるときに限って待ちに入れます。

それは、さもなければ、そのスレッドは際限なく待ち続けることになりかねないからです。

Objector 9B
分かるわよ、それで?

Special-Student-7
そのスレッドが"s_unoConnectionUsingThreadsCondition"を対象として待つには、"s_unoConnectionUsingThreadsCondition"がロック済みでなければなりませんが、"s_unoConnectionMaintainingThreadsCondition"もロックされていなければなりません、"s_unoConnectionInformation"をチェックするためには(なぜなら、"s_unoConnectionMaintainingThreadsCondition"が"s_unoConnectionInformation"をプロテクトしているから)。

Objector 9B
だから?そのスレッドは"s_unoConnectionMaintainingThreadsCondition"を最初にロックして、次に"s_unoConnectionUsingThreadsCondition"をロックできないの?

Special-Student-7
できません、なぜなら、それでは、そのスレッドが待っている間、"s_unoConnectionMaintainingThreadsCondition"がロックされたままになってしまうから。.

Objector 9B
それが何?

Special-Student-7
もしそうなれば、"l_unoConnectingThread"はロックアウトされたままになってしまい、新たなコネクションを確立できないことになり、したがって、プログラムは永遠に固まってしまうでしょう。

Objector 9B
もちろん . . .。そのスレッドは、"s_unoConnectionInformation"をチェックして"s_unoConnectionMaintainingThreadsCondition"をリリースし、次に"s_unoConnectionUsingThreadsCondition"をロックできないの?

Special-Student-7
できません、なぜなら、"s_unoConnectionInformation"は、"s_unoConnectionMaintainingThreadsCondition"がリリースされ"s_unoConnectionUsingThreadsCondition"がロックされる前に変更されるかもしれないからで、そうなると、チェックは信頼できないものになります。

Objector 9B
なるほど、でも、"s_unoConnectionMaintainingThreadsCondition"は、そのスレッドが待ち始める前にリリースされてるでしょう、あなたのコードでは。あなたの理論によれば、スレッドが待ち始める前にコネクションが既に確立済みということになるかもしれないじゃないの。

Special-Student-7
そうなるかもしれませんが、それは実のところ問題ありません、なぜなら、"s_unoConnectionUsingThreadsCondition"がロックされているので、目覚ましコールはいずれにせよまだディスパッチされているはずなく、そこがポイントです。つまり、コネクションは確かに既に確立されている可能性がありますが、それにもかかわらず、そのスレッドは目覚ましコールを受けると保証されています。

Objector 9B
えーと、. . . そのようね。

Special-Student-7
"l_unoConnectingThread"スレッドについて言うと、それが待ち始める時、コネクションは既に切断されているかもしれません、しかし、その切断はまだ検知されていないと保証されています、したがって、目覚ましコールはまだディスパッチされていないと保証されています。

Objector 9B
それはなぜかと言うと、"s_unoConnectionMaintainingThreadsCondition"は"l_unoConnectingThread"スレッドによってロックされており、コネクション切断検出スレッドは、切断を検出する前に、"s_unoConnectionMaintainingThreadsCondition"をロックしなければならないからね。

ははあ、ポイントは、どのスレッドも、予期される目覚ましコールが、そのスレッドが待ち始める前に既にディスパッチ済みということがないことを確実にしなければならないということね。

Special-Student-7
はい、なぜなら、さもなければ、待っているスレッドは永久に起こされないということになるだろうから。

そして、手当てをしなければならない第3のポイントは、任意のスレッドは、他のスレッドが走らなければならない時には、待たなければならないということです。

Objector 9B
はあ?そうは思わないけど。マルチスレッディングなんでしょう?複数のスレッドが同時に走れるでしょう?

Special-Student-7
必ずしもそうではありません、実行時環境によっては。複数スレッドは、もしも、単一コアの単一CPUしかなかった場合、本当には同時に走りません、したがって、別スレッドは、走行中スレッドが走行し続けている間、タイムスライスを全然与えられないかもしれません、走行中スレッドが待つまで。

Objector 9B
ああ、私は間違ったことを言ってしまった!取り返しのつかない過ちだ!私はもう終わり!

Special-Student-7
いえ、いえ、マダム、あなたは大丈夫です。あなたは、そんな貧しい環境を想像しなかっただけです。

Objector 9B
うん?そうね、それは貧しすぎるわ!シングルコア?私の世界には無縁のことよ!

Special-Student-7
とにかく、"l_unoWorkingThread"は、コネクション切断検出スレッドが走ることを保証するためには、待たなければなりません。

Objector 9B
でも、"l_unoWorkingThread"は周期的にスリープするんだから、コネクション切断検出スレッドは、"l_unoWorkingThread"がスリープしている間にチャンスを与えられるでしょう。

Special-Student-7
そうだと思いますが、"sleep"はそこにないものと想定してください: "sleep"がそこにある理由は、ただ、ひっきりなしのメッセージ出力が煩わしいというだけのことです。

Objector 9B
プログラムがWebサーバーだったらどうなの?

Special-Student-7
もしも、プログラムがWebサーバーだったら、それはループではなく、コネクション切断検出スレッドはユーザーリクエストの合間に呼び出されるでしょう。


7: 動かせるサンプル


Special-Student-7
ここに動かせるサンプルがあり、それは、LibreOfficeまたはApache OpenOfficeインスタンスによってローカルに格納されたファイルは、何であろうが自動的にそのセーブファイルを作るものです(注意として、本ケースでは、LibreOfficeまたはApache OpenOfficeインスタンスはローカルであると想定されています)。

メインプロジェクトは'connectionAwareExternalUnoClients'であり、そのプロジェクトをビルドする方法はある以前の記事に記述されています。

プログラムは以下のようにして実行できます。

@bash or CMD ソースコード
gradle i_executeJarTask -Pi_commandLineArguments="socket,host=localhost,port=2002,tcpNoDelay=1;urp;StarOffice.ComponentContext"

注意として、そのコードは、上記コードのようにフラットに書かれてはいませんが、行なわれていることは同じです、私のユーティリティクラスである、'theBiasPlanet.unoUtilities.processesHandling.UnoProcessEnvironment.UnoProcessEnvironment'、'theBiasPlanet.unoUtilities.connectionsHandling.UnoConnectionConnector.UnoConnectionConnector'、'theBiasPlanet.unoUtilities.connection.UnoConnection.UnoConnection'などの中にしまわれて。   .

Objector 9A
"フラットに"って何だ?

Special-Student-7
上記コードは、説明目的のためにのみ書かれたものです。私は通常そのようには書きません。私は通常、もっと適切に物事をクラス群として構成します。

Objector 9A
それはお気楽か?

Special-Student-7
例えば、あの、ひとかたまりのコネクション確立手順は定型コードであって、それを各UNO外部クライアントにフラットに書くことを私は喜びません、したがって、私は、それを、あるユーティリティクラス内に閉じ込めます。UNOには'com.sun.star.connection.XConnection'および'com.sun.star.bridge.XBridge'のオブジェクトがありますが、その区別は実際のところ私には無用です、したがって、私は、1つのUNOコネクション全体を代表する単一のクラスを持ちます。特に、UNOは、同一のUNOオブジェクトのさまざまなUNOプロキシを取り出さないとならないという点において扱いにくいので、私は、UNOコンポーネントに対応する(各UNOインターフェイスにではなく)あるユーティリティクラス('theBiasPlanet.unoUtilities.pointers.UnoObjectPointer')を持っており、そのユーティリティクラスにそうしたさまざまなUNOプロキシを操作させます。

Objector 9A
それはお気楽か?

Special-Student-7
. . . そうです、私にとっては。


参考資料


<このシリーズの前の記事 | このシリーズの目次 | このシリーズの次の記事>