2019-09-01

26: A Safe, Attentive LibreOffice/OpenOffice Windows Service

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

As a files conversion server, etc.. One that doesn't brutally kill the office instance together with the ongoing user requests. With a workable sample

Topics


About: UNO (Universal Network Objects)
About: LibreOffice
About: Apache OpenOffice
About: C#

The table of contents of this article


Starting Context



Target Context


  • The reader will know how to create a safe, attentive C# LibreOffice or Apache OpenOffice Windows service and have a workable sample.

Orientation


There is an article on how to create a safe, attentive LibreOffice or Apache OpenOffice daemon.


Main Body

Stage Direction
Here are Special-Student-7, Tony (a C# programmer), Abby (a C# programmer), Morris (a C++ programmer) in front of a computer.


0: A Note


Special-Student-7
While this article creates a LibreOffice or Apache OpenOffice Windows service in C#, you can create one in also Visual Basic.NET, if you will.

Creating any Windows service in Visual Basic.NET will be on the same principle with creating one in C#, and any C# UNO code should be able to be easily translated into Visual Basic.NET UNO code.

Tony
You may have made some enemies of Visual Basic.NET programmers, saying such a breezy thing.

Special-Student-7
May I? Well, creating a Windows service in Visual Basic.NET should be as easy as almost just choosing some project options at least with Visual Studio IDE, and UNO programming in .NET uses the same managed UNO assemblies in whatever programming language, so, it should be basically the same in any .NET programming language.

Tony
Visual Basic.NET programmers are basically perfunctory programmers, who don't want to see any program in another programming language, to say nothing of translating. Why else have they chosen Visual Basic instead of more serious programming languages?

Special-Student-7
You may have made many enemies of Visual Basic.NET programmers, sir.

Anyway, I do not mind showing Visual Basic.NET code if there is much demand, but I am not assuming that there is.

Morris
I can create one in C++, can't I? I don't want to turn to C# just for Windows.

Special-Student-7
Yes, certainly you can. But creating a Windows service in C++ requires exploiting Win32 API as far as I know, and I myself did it a long time ago, but I would not go for it when creating one in .NET is so easy.

Morris
I don't intend to play around with other programming languages! I don't say I can't, but the patchiness offends my aesthetic!

Special-Student-7
As I said, you can create one in C++, and the UNO code for it can be the same with of the office daemon.


1: What the Office Windows Service Should Do


Special-Student-7
You may think that what the office Windows service has to do is to just invoke an office instance at startup and kill the instance process at shutdown, but that is not the case.

Tony
I didn't think so, for your information.

Abby
Then, what more did you think it had to do?

Tony
Well, let that guy talk about it; I shouldn't take away what he wants to say.

Special-Student-7
Thank you, sir.

Here, we will have a versatile, safe and attentive Windows service that takes care of these 2 things: 1) making the Windows service prevent any shutdown of the office instance except by itself and 2) shutting the office instance safely and attentively when the Windows service itself is shutting down.

Tony
That is what I thought.

Abby
I don't understand 1): who else would try to shut the office instance down, Tony?

Tony
Well, let that guy talk.

Special-Student-7
A typical problem is 'soffice --convert-to'.

Any 'soffice --convert-to' execution tries to shut the office instance down, at least for the versions I have tried, and we want to prevent it.

Tony
Of course! Didn't you know it, Abby?

Abby
2) seems to mean closing the opened documents before it shuts the office instance down.

Tony
Of course!

Special-Student-7
In fact, our attentive Windows service will not barbarically close the opened documents that are being used by some clients; it will let the clients complete their critical sessions and close the documents by themselves, and only after that, it will shut the office instance down.

Tony
I mean "Of course not"; it would be barbaric.


2: How the Office Windows Service Can Do So


Special-Student-7
Our office Windows service not only starts an office instance up, but also becomes a UNO client to the office instance.

So, of course, the office instance has to be made a UNO server, and you have to know how to make your program a UNO client.

How to create a C# Windows service is explained in an article, by the way.

Anyway, in order to accomplish "1)" in the previous section, the office Windows service registers itself as an office-instance-termination-attempts listener into the office instance and vetoes any office-instance-termination-attempt unless it itself is shutting down.

Abby
"vetoes"? Will the office instance resign shutting down just because someone said "NO"? How deferential!

Special-Student-7
Yes, any UNO client can veto.

As for "2)", the office Windows service waits until all the ongoing critical sessions of the UNO clients have been completed and then courteously asks the office instance to die instead of mercilessly killing the office instance.

Abby
I think, also that is about vetoing: the UNO clients can veto while they have ongoing critical sessions.

Special-Student-7
Unfortunately, it does not go that way.

Tony
Yeah, you were a bit shallow-minded, Abby.

Abby
But why? It seems to go fine, Tony.

Tony
Let that guy talk.

Special-Student-7
The problem is that any shutdown attempt first tries to close the opened documents and only after all the opened documents have been closed, lets the UNO clients veto; so, vetoing does not protect critical sessions.

Tony
Yes, you didn't think about it, Abby.

Abby
How could I have expected the mechanism to be so dull?! They should know that a vetoing client usually will not want its opened documents to be closed!

Special-Student-7
In fact, the UNO client can veto closing of any opened document, but the problem is that any shutdown attempt abandons the effort at the 1st encounter with any document-closing-veto; so, all the UNO clients except one cannot recognize that the office instance has been planned to be shut down, keeping newly entering critical blocks.

Tony
Yeah, you had to think of that the clients should be prohibited from newly entering critical sessions, or the windows service might not be able to shut the office instance for ever, Abby.

Abby
Well, you are right. But what is the solution, then, Tony?

Tony
Let that guy talk.

Special-Student-7
We will have a counter of critical sessions; each UNO client increments the counter whenever it enters any critical block and decrements the counter whenever it leaves the block.

As the counter has to be shared by the office Windows service and the UNO clients, of course, it cannot be a commonplace local variable in the office Windows service, but a variable in a singleton UNO object shared by all the participants, which are the office Windows service and the UNO clients.

As we do not want to allow the UNO clients new critical sessions after the office instance has been planned to be shut down, we will have also a the-office-instance-is-planned-to-be-shut-down flag, which is of course also in the singleton UNO object; the UNO clients does not enter any critical block any more if the flag is up.

Tony
You've got it. Exactly my solution.

Special-Student-7
Thank you, sir.

Abby
But what if a UNO client has aborted, leaving some critical session counts?

Special-Student-7
That is certainly a possibility to be considered, and I have thought of 2 solutions.

The 1st one is to not make the office Windows service wait indefinitely but let have a grace period after which the office Windows service will shut the office instance anyway.

The 2nd one is to have an external tool that lets the operator decrement such unattended counts.

Our singleton UNO object can manage the critical session counts per UNO client and disclose them to the operator, probably via the tool.

Abby
You say "singleton UNO object", but what is it exactly? How can it be created?

Special-Student-7
The singleton UNO object is a UNO object that is registered in a property of a UNO objects context; as far as the UNO objects context is shared, the UNO object will be shared.

How to create it is explained in an article.


3: Some Code Fragments of a Safe Attentive Office Windows Service


Special-Student-7
Let us see the important parts of a safe attentive office Windows service.

An entire office Windows service can be gotten in a later section.

As we need the UNO object singleton, we need the UNO interface that is to be implemented by the UNO component.

How to create any UNO interface is explained in an article.

This is our UNOIDL definition of the UNO interface.

@UNOIDL Source Code
#ifndef __theBiasPlanet_unoDatumTypes_unoUtilities_officeInstance_XOfficeInstance_idl__
#define __theBiasPlanet_unoDatumTypes_unoUtilities_officeInstance_XOfficeInstance_idl__

#include <com/sun/star/uno/XInterface.idl>
#include <com/sun/star/lang/IllegalArgumentException.idl>
#include <com/sun/star/uno/Exception.idl>

module theBiasPlanet {
	module unoDatumTypes {
		module unoUtilities {
			module officeInstance {
				interface XOfficeInstance {
					interface ::com::sun::star::uno::XInterface;
					boolean incrementCriticalSessionCount ( [in] string a_connectionIdentification);
					void decrementCriticalSessionCount ( [in] string a_connectionIdentification);
					long getCriticalSessionCount ();
					boolean serverIsPlannedToBeTerminated ();
				};
			};
		};
	};
};
#endif


Only 'incrementCriticalSessionCount' but not 'decrementCriticalSessionCount' has the 'boolean' return, which is because 'incrementCriticalSessionCount' fails when the office instance has been planned to be shut down, while 'decrementCriticalSessionCount' does not fail; only when 'incrementCriticalSessionCount' has succeeded, the UNO client is supposed to enter the critical block.

Tony
Of course.

Special-Student-7
This is the code of our singleton UNO component, where 'theBiasPlanet.unoUtilitiesTests.localUnoObjectsTest1.TestUnoComponent' is from another article on creating any UNO component in C#.

theBiasPlanet/unoUtilitiesTests/officeDaemonTest1/OfficeInstance.cs

@C# Source Code
namespace theBiasPlanet {
	namespace unoUtilitiesTests {
		namespace officeWindowsServiceTest1 {
			using System;
			using System.Runtime.InteropServices;
			using System.Threading;
			using unoidl.com.sun.star.container;
			using unoidl.com.sun.star.frame;
			using unoidl.com.sun.star.lang;
			using unoidl.com.sun.star.uno;
			using unoidl.com.sun.star.util;
			using unoidl.theBiasPlanet.unoDatumTypes.unoUtilities.officeInstance;
			using theBiasPlanet.unoUtilitiesTests.localUnoObjectsTest1;
			
			public class OfficeInstance: TestUnoComponent, XOfficeInstance {
				private XComponentContext i_underlyingRemoteUnoObjectsContextInXComponentContext;
				private	Object i_threadsCondition;
				private	Int32 i_criticalSessionCount;
				private	Boolean i_serverIsPlannedToBeTerminated;
				public static String c_officeInstanceSingletonUrl;
				
				static OfficeInstance () {
					c_officeInstanceSingletonUrl = "/singletons/theBiasPlanet.unoUtilitiesTests.officeDaemonTest1.OfficeInstance";
				}
				
				public OfficeInstance (XComponentContext a_underlyingRemoteUnoObjectsContextInXComponentContext) {
					i_threadsCondition = new Object ();
					i_criticalSessionCount = 0;
					i_serverIsPlannedToBeTerminated = false;
					i_underlyingRemoteUnoObjectsContextInXComponentContext = a_underlyingRemoteUnoObjectsContextInXComponentContext;
				}
				
				~OfficeInstance () {
				}
				
				public virtual Boolean incrementCriticalSessionCount (String a_connectionIdentification) {
					lock (i_threadsCondition) {
						if (i_serverIsPlannedToBeTerminated) {
							return false;
						}
						else {
							i_criticalSessionCount ++;
							Console.Out.WriteLine (String.Format ("### the critical sessions count has been incremented to '{0:d}'.", i_criticalSessionCount));
							Console.Out.Flush ();
							return true;
						}
					}
				}
				
				public virtual void decrementCriticalSessionCount (String a_connectionIdentification) {
					lock (i_threadsCondition) {
						i_criticalSessionCount --;
						Console.Out.WriteLine (String.Format ("### the critical sessions count has been decremented to '{0:d}'.", i_criticalSessionCount));
						Console.Out.Flush ();
						if (i_criticalSessionCount <= 0) {
							Monitor.PulseAll (i_threadsCondition);
						}
					}
				}
				
				public virtual Int32 getCriticalSessionCount () {
					lock (i_threadsCondition) {
						return i_criticalSessionCount;
					}
				}
				
				public virtual Boolean serverIsPlannedToBeTerminated () {
					lock (i_threadsCondition) {
						return i_serverIsPlannedToBeTerminated;
					}
				}
				
				public virtual void setServerAsPlannedToBeTerminated () {
					lock (i_threadsCondition) {
						i_serverIsPlannedToBeTerminated = true;
					}
				}
				
				public virtual Boolean waitUntilNoCriticalSession (Int32 a_timeOutInMilliseconds) {
					lock (i_threadsCondition) {
						if (i_criticalSessionCount > 0) {
							if (a_timeOutInMilliseconds == -1) {
								Monitor.Wait (i_threadsCondition);
								return true;
							}
							else {
								return Monitor.Wait (i_threadsCondition, a_timeOutInMilliseconds);
							}
						}
						else {
							return true;
						}
					}
				}
				
				public virtual Boolean terminate (Int32 a_gracePeriodInMillisecondsBeforeForcing) {
					setServerAsPlannedToBeTerminated ();
					waitUntilNoCriticalSession (a_gracePeriodInMillisecondsBeforeForcing);
					XDesktop l_underlyingUnoDesktopInXDesktop = (XDesktop) (i_underlyingRemoteUnoObjectsContextInXComponentContext.getValueByName ("/singletons/com.sun.star.frame.theDesktop").Value);
					XEnumeration l_underlyingChildObjectsIteratorInXEnumeration = l_underlyingUnoDesktopInXDesktop.getComponents ().createEnumeration ();
					XCloseable l_underlyingChildObjectInXCloseable = null;
					string l_documentUrl = null;
					while (l_underlyingChildObjectsIteratorInXEnumeration.hasMoreElements ()) {
						try {
							l_underlyingChildObjectInXCloseable = (XCloseable) (l_underlyingChildObjectsIteratorInXEnumeration.nextElement ().Value);
							if (l_underlyingChildObjectInXCloseable == null) {
								continue;
							}
							l_documentUrl = ( (XStorable2) l_underlyingChildObjectInXCloseable).getLocation ();
							try {
								l_underlyingChildObjectInXCloseable.close (false);
								Console.Out.WriteLine (String.Format ("The document '{0:s}' has been closed.", l_documentUrl));
								Console.Out.Flush ();
							}
							catch (CloseVetoException) {
								Console.Out.WriteLine (String.Format ("The document '{0:s}' has been vetoed to be closed.", l_documentUrl));
								Console.Out.Flush ();
								( (XComponent) l_underlyingChildObjectInXCloseable).dispose ();
								Console.Out.WriteLine (String.Format ("The document '{0:s}' has been disposed.", l_documentUrl));
								Console.Out.Flush ();
							}
							catch (DisposedException) {
								Console.Out.WriteLine (String.Format ("The document '{0:s}' has been disposed.", l_documentUrl));
								Console.Out.Flush ();
							}
						}
						catch (System.Exception l_exception) {
							Console.Out.WriteLine (String.Format ("An error has occurred: {0:s}.", l_exception.ToString ()));
							Console.Out.Flush ();
						}
					}
					Boolean l_shutdownTryResult = false;
					// loops because the shutdown may be vetoed.
					while (true) {
						try {
							l_shutdownTryResult = l_underlyingUnoDesktopInXDesktop.terminate ();
						}
						catch (DisposedException) {
							return true;
						}
						if (l_shutdownTryResult) {
							return l_shutdownTryResult;
						}
						Thread.Sleep (1000);
					}
				}
			}
		}
	}
}


Abby
. . . Shouldn't also those methods like 'terminate' be defined in the UNO interface?

Special-Student-7
The methods like 'terminate' are not supposed to be called from UNO clients, according to my present plan.

Abby
So?

Special-Student-7
The purpose of the UNO interface is to disclose its methods to UNO clients; so, what are not disclosed do not need to be there.

Abby
So, the methods like 'terminate' are called by the Windows service . . . how?

Special-Student-7
The singleton will live in the Windows service, so, the singleton will be a local object for the Windows service, which will be able to call any public method of the local object as call any public method of any usual C# object.

Tony
Of course.

Special-Student-7
"i_serverIsPlannedToBeTerminated" is the the-office-instance-is-planned-to-be-shut-down flag and "i_criticalSessionCount" is the critical sessions counter.

Whenever the flag is down or the counter is plus, any shutdown is vetoed.

The "incrementCriticalSessionCount" method allows the counter to be incremented only when the flag is down, while the "decrementCriticalSessionCount" method allows the counter to be decremented anytime, being supposed to be called only after the counter has been successfully incremented.

The 'terminate' method takes a grace period after which it tries to shut the office instance down anyway regardless of some overlong critical session or phantom session counts.

The method raises the flag and waits until the counter becomes '0', with the grace period.

After that, the method tries to close the opened documents (if any) before it tries to shut the office instance down, and that may not be absolutely necessary because the shutdown will try to close the documents anyway, but the method also disposes the close-vetoed documents and lets us know what the opened documents were.

Abby
Well, I'm OK if the shutdown is not stuck by the left-opened documents.

Special-Student-7
The instance of the UNO component can be registered into or unregistered from the office instance as a singleton like this, where "l_underlyingRemoteUnoObjectsContextInXComponentContext" is a UNO objects context to the office instance.

@C# Source Code
namespace theBiasPlanet {
	namespace unoUtilitiesTests {
		namespace officeWindowsServiceTest1 {
			using System;
			~
			using unoidl.com.sun.star.container;
			~
			using unoidl.com.sun.star.uno;
			~
			using unoidl.theBiasPlanet.unoDatumTypes.unoUtilities.officeInstance;
			~
			
			public class Test1Test {
				public void main (String [] a_argumentsArray) {
								~
								XNameContainer l_underlyingRemoteUnoObjectsContextInXNameContainer = (XNameContainer) l_underlyingRemoteUnoObjectsContextInXComponentContext;
								~
								OfficeInstance l_underlyingOfficeInstance = new OfficeInstance (l_underlyingRemoteUnoObjectsContextInXComponentContext);
								XOfficeInstance l_underlyingOfficeInstanceInXOfficeInstance = l_underlyingOfficeInstance;
								~
								if (l_underlyingRemoteUnoObjectsContextInXComponentContext.getValueByName (OfficeInstance.c_officeInstanceSingletonUrl).Value == null) {
									try {
										l_underlyingRemoteUnoObjectsContextInXNameContainer.insertByName (OfficeInstance.c_officeInstanceSingletonUrl, new uno.Any (typeof (XOfficeInstance), l_underlyingOfficeInstanceInXOfficeInstance));
									}
									catch (ElementExistException) {
									}
								}
								~
									~
									try {
										l_underlyingRemoteUnoObjectsContextInXNameContainer.removeByName (OfficeInstance.c_officeInstanceSingletonUrl);
									}
									catch (NoSuchElementException) {
									}
									~
				}
			}
		}
	}
}


Tony
Of course.

Abby
Can't you say other than "Of course"?

Tony
They are too obvious for people like me.

Abby
What people like you? Cutups?

Tony
The proficient.

Abby
Of course.

Special-Student-7
This is the office-instance-termination-attempts listener.

theBiasPlanet/unoUtilitiesTests/officeDaemonTest1/OfficeTerminationQueryListener.cs

@C# Source Code
namespace theBiasPlanet {
	namespace unoUtilitiesTests {
		namespace officeWindowsServiceTest1 {
			using System;
			using unoidl.com.sun.star.frame;
			using unoidl.theBiasPlanet.unoDatumTypes.unoUtilities.officeInstance;
			using theBiasPlanet.unoUtilitiesTests.localUnoObjectsTest1;
			
			public class OfficeTerminationQueryListener: TestUnoComponent, XTerminateListener {
				private XOfficeInstance i_underlyingOfficeInstanceInXOfficeInstance;
				
				public OfficeTerminationQueryListener (XOfficeInstance a_underlyingOfficeInstanceInXOfficeInstance) {
					i_underlyingOfficeInstanceInXOfficeInstance = a_underlyingOfficeInstanceInXOfficeInstance;
				}
				
				~OfficeTerminationQueryListener () {
				}
				
				public void queryTermination (unoidl.com.sun.star.lang.EventObject a_event) {
					Console.Out.WriteLine ("### The termination of the office instance is queried.");
					Console.Out.Flush ();
					if (! (i_underlyingOfficeInstanceInXOfficeInstance.serverIsPlannedToBeTerminated ()) || i_underlyingOfficeInstanceInXOfficeInstance.getCriticalSessionCount () > 0) {
						Console.Out.WriteLine ("### And vetoed.");
						Console.Out.Flush ();
						throw new TerminationVetoException ();
					}
					else {
						Console.Out.WriteLine ("### And accepted.");
						Console.Out.Flush ();
					}
				}
				
				public void notifyTermination (unoidl.com.sun.star.lang.EventObject a_event) {
					Console.Out.WriteLine ("### The termination of the office instance is notified.");
					Console.Out.Flush ();
				}
				
				public void disposing (unoidl.com.sun.star.lang.EventObject a_event) {
				}
			}
		}
	}
}


It vetoes any shutdown while the flag is down or the counter is plus.

The instance of it can be registered into or unregistered from the office instance like this.

@C# Source Code
namespace theBiasPlanet {
	namespace unoUtilitiesTests {
		namespace officeWindowsServiceTest1 {
			using System;
			~
			using unoidl.com.sun.star.frame;
			using unoidl.com.sun.star.uno;
			~
			
			public class Test1Test {
				public void main (String [] a_argumentsArray) {
					~
								String l_com_sun_star_frame_theDesktopSingletonUrl = "/singletons/com.sun.star.frame.theDesktop";
								~
								XDesktop l_underlyingUnoDesktopInXDesktop = (XDesktop) (l_underlyingRemoteUnoObjectsContextInXComponentContext.getValueByName (l_com_sun_star_frame_theDesktopSingletonUrl).Value);
								if (l_underlyingUnoDesktopInXDesktop != null) {
									XTerminateListener l_underlyingOfficeTerminationQueryListenerInXTerminateListener = new OfficeTerminationQueryListener (l_underlyingOfficeInstanceInXOfficeInstance);
									l_underlyingUnoDesktopInXDesktop.addTerminateListener (l_underlyingOfficeTerminationQueryListenerInXTerminateListener);
									~
									l_underlyingUnoDesktopInXDesktop.removeTerminateListener (l_underlyingOfficeTerminationQueryListenerInXTerminateListener);
								}
								~
				}
			}
		}
	}
}


Abby
Of course.

Tony
Don't usurp my line.

Special-Student-7
As a whole, the office Windows service starts an office instance up, connects to the office instance, registers an office-instance-termination-attempts listener instance into the office instance, registers a singleton UNO component instance into the office instance as a singleton, waits for the 'OnStop' method to be called, and calls the 'terminate' method.


4: Example UNO Client Critical Blocks


Special-Student-7
This scheme of safe attentive office Windows service does not work without the UNO clients' behaving themselves.

Any UNO clients has to have each critical session like this, where "l_underlyingRemoteUnoObjectsContextInXComponentContext" is a UNO objects context to the office instance. The code is, in fact, exactly the same with that for the office daemon, as can be reasonably expected.

@Java Source Code
package theBiasPlanet.unoUtilitiesTests.criticalSessionsAwareExternalUnoClientTest1;

import com.sun.star.uno.UnoRuntime;
~
import theBiasPlanet.unoDatumTypes.unoUtilities.officeInstance.XOfficeInstance;
~

public class Test1Test {
	public static void main (String [] a_argumentsArray) {
		~
				String l_officeInstanceSingletonUrl = "/singletons/theBiasPlanet.unoUtilitiesTests.officeDaemonTest1.OfficeInstance";
				Object l_underlyingOfficeInstanceInObject = l_underlyingRemoteUnoObjectsContextInXComponentContext.getValueByName (l_officeInstanceSingletonUrl);
				XOfficeInstance l_underlyingOfficeInstanceInXOfficeInstance = null;
				if (l_underlyingOfficeInstanceInObject instanceof com.sun.star.uno.Any) {
					l_underlyingOfficeInstanceInXOfficeInstance = UnoRuntime.queryInterface (XOfficeInstance.class, ( (com.sun.star.uno.Any) l_underlyingOfficeInstanceInObject).getObject ());
				}
				else if (l_underlyingOfficeInstanceInObject instanceof XInterface) {
					l_underlyingOfficeInstanceInXOfficeInstance = UnoRuntime.queryInterface (XOfficeInstance.class, l_underlyingOfficeInstanceInObject);
				}
				if (l_underlyingOfficeInstanceInXOfficeInstance == null) {
					System.out.println ("### the singleton is not there.");
					System.out.flush ();
				}
				else {
					String l_unoClientIdentity = "Java client A";
					while (true) {
						if (l_underlyingOfficeInstanceInXOfficeInstance.incrementCriticalSessionCount (l_unoClientIdentity)) {
							try {
								System.out.println ("### a critical session Start");
								System.out.flush ();
								Thread.sleep (15000);
							}
							finally {
								l_underlyingOfficeInstanceInXOfficeInstance.decrementCriticalSessionCount (l_unoClientIdentity);
								System.out.println ("### a critical session End");
								System.out.flush ();
							}
						}
						else {
							System.out.println ("### the UNO server seems to have been planned to be terminated.");
							System.out.flush ();
							break;
						}
					}
				}
		~
	}
}


@C++ Source Code
#include <iostream>
~
#include <string>
~
#include <thread>
~
#include <com/sun/star/uno/Any.hxx>
~
#include <com/sun/star/uno/Reference.hxx>
~
#include <com/sun/star/uno/XInterface.hpp>
~
#include "theBiasPlanet/unoDatumTypes/unoUtilities/officeInstance/XOfficeInstance.hpp"

using namespace ::std;
~
using namespace ::com::sun::star::uno;
~
using namespace ::theBiasPlanet::unoDatumTypes::unoUtilities::officeInstance;
~

namespace theBiasPlanet {
	namespace unoUtilitiesTests {
		namespace criticalSessionsAwareExternalUnoClientTest1 {
			Test1Test::CriticalSessionFinally::CriticalSessionFinally (string const & a_unoClientIdentity, Reference <XOfficeInstance> & a_underlyingOfficeInstanceInXOfficeInstance): i_unoClientIdentity (a_unoClientIdentity), i_underlyingOfficeInstanceInXOfficeInstance (a_underlyingOfficeInstanceInXOfficeInstance) {
			}
			
			Test1Test::CriticalSessionFinally::~CriticalSessionFinally () noexcept (false) {
				i_underlyingOfficeInstanceInXOfficeInstance->decrementCriticalSessionCount (UnoExtendedStringHandler::getOustring (i_unoClientIdentity));
				cout << string ("### a critical session End") << endl << flush;
			}
			
			int Test1Test::main (int const & a_argumentsNumber, char const * const a_argumentsArray []) {
					~
						string l_officeInstanceSingletonUrl ("/singletons/theBiasPlanet.unoUtilitiesTests.officeDaemonTest1.OfficeInstance");
						Any l_underlyingOfficeInstanceInAny (l_underlyingRemoteUnoObjectsContextInXComponentContext->getValueByName (UnoExtendedStringHandler::getOustring (l_officeInstanceSingletonUrl)));
						Reference <XOfficeInstance> l_underlyingOfficeInstanceInXOfficeInstance (* ( (Reference <XInterface> *) l_underlyingOfficeInstanceInAny.getValue ()), UNO_QUERY);
						if (! (l_underlyingOfficeInstanceInXOfficeInstance.is ())) {
							cout << string ("### the singleton is not there.") << endl << flush;
						}
						else {
							string l_unoClientIdentity ("C++ client A");
							while (true) {
								if (l_underlyingOfficeInstanceInXOfficeInstance->incrementCriticalSessionCount (UnoExtendedStringHandler::getOustring (l_unoClientIdentity))) {
									{
										CriticalSessionFinally l_criticalSessionFinally (l_unoClientIdentity, l_underlyingOfficeInstanceInXOfficeInstance);
										cout << string ("### a critical session Start") << endl << flush;
										this_thread::sleep_for (milliseconds (15000));
									}
								}
								else {
									cout << string ("### the UNO server seems to have been planned to be terminated.") << endl << flush;
									break;
								}
							}
						}
						~
			}
		}
	}
}


@C# Source Code
namespace theBiasPlanet {
	namespace unoUtilitiesTests {
		namespace criticalSessionsAwareExternalUnoClientTest1 {
			using System;
			~
			using System.Threading;
			~
			using unoidl.theBiasPlanet.unoDatumTypes.unoUtilities.officeInstance;
			~
			public class Test1Test {
				public void main (String [] a_argumentsArray) {
					~
								String l_officeInstanceSingletonUrl = "/singletons/theBiasPlanet.unoUtilitiesTests.officeDaemonTest1.OfficeInstance";
								uno.Any l_underlyingOfficeInstanceInAny = l_underlyingRemoteUnoObjectsContextInXComponentContext.getValueByName (l_officeInstanceSingletonUrl);
								XOfficeInstance l_underlyingOfficeInstanceInXOfficeInstance = null;
								if (l_underlyingOfficeInstanceInAny.hasValue ()) {
									l_underlyingOfficeInstanceInXOfficeInstance = (XOfficeInstance) l_underlyingOfficeInstanceInAny.Value;
								}
								if (l_underlyingOfficeInstanceInXOfficeInstance == null) {
									Console.Out.WriteLine ("### the singleton is not there.");
									Console.Out.Flush ();
								}
								else {
									String l_unoClientIdentity = "C# client A";
									while (true) {
										if (l_underlyingOfficeInstanceInXOfficeInstance.incrementCriticalSessionCount (l_unoClientIdentity)) {
											try {
												Console.Out.WriteLine ("### a critical session Start");
												Console.Out.Flush ();
												Thread.Sleep (15000);
											}
											finally {
												l_underlyingOfficeInstanceInXOfficeInstance.decrementCriticalSessionCount (l_unoClientIdentity);
												Console.Out.WriteLine ("### a critical session End");
												Console.Out.Flush ();
											}
										}
										else {
											Console.Out.WriteLine ("### the UNO server seems to have been planned to be terminated.");
											Console.Out.Flush ();
											break;
										}
									}
								}
				~
			}
		}
	}
}


@Python Source Code
from typing import Any
from typing import List
from typing import cast
~
import sys
import time
import uno
~
from uno import Any as UnoAny
~
XOfficeInstance: Any = uno.getClass ("theBiasPlanet.unoDatumTypes.unoUtilities.officeInstance.XOfficeInstance")

class Test1Test:
	@staticmethod
	def main (a_arguments: List [str]) -> None:
		~
				l_officeInstanceSingletonUrl: str = "/singletons/theBiasPlanet.unoUtilitiesTests.officeDaemonTest1.OfficeInstance"
				l_underlyingOfficeInstanceInObject: object = l_underlyingRemoteUnoObjectsContextInXComponentContext.getValueByName (l_officeInstanceSingletonUrl)
				l_underlyingOfficeInstanceInXOfficeInstance: "XOfficeInstance" = None
				if isinstance (l_underlyingOfficeInstanceInObject, UnoAny):
					l_underlyingOfficeInstanceInXOfficeInstance = cast (XOfficeInstance, cast (UnoAny, l_underlyingOfficeInstanceInObject).value)
				elif l_underlyingOfficeInstanceInObject is not None:
					l_underlyingOfficeInstanceInXOfficeInstance = cast (XOfficeInstance, l_underlyingOfficeInstanceInObject)
				if l_underlyingOfficeInstanceInXOfficeInstance is None:
					sys.stdout.write ("### the singleton is not there.\n")
					sys.stdout.flush ()
				else:
					l_unoClientIdentity: str = "Python client A"
					while True:
						if l_underlyingOfficeInstanceInXOfficeInstance.incrementCriticalSessionCount (l_unoClientIdentity):
							try:
								sys.stdout.write ("### a critical session Start\n")
								sys.stdout.flush ()
								time.sleep (15)
							finally:
								l_underlyingOfficeInstanceInXOfficeInstance.decrementCriticalSessionCount (l_unoClientIdentity)
								sys.stdout.write ("### a critical session End\n")
								sys.stdout.flush ()
						else:
							sys.stdout.write ("### the UNO server seems to have been planned to be terminated.\n")
							sys.stdout.flush ()
							break


Tony
If there is someone who does not behave, . . .

Abby
Like you?

Special-Student-7
Usually, such a client will not even increment the counter, so, will not cause any harm to the Windows service or the other clients, but just its critical sessions will not be took heed of.

Tony
If there is a selfish guy who just increments the counter, . . .

Abby
Like you?

Special-Student-7
In fact, that will probably cause no harm to the other clients, because it will be just a matter of that the office instance will not be shut down until the grace period expires.

Tony
If there is a prankster who just decrements the counter, . . .

Abby
Is there such a guy? It's sabotage!

Special-Student-7
In fact, it may be possible by an overoptimistic person who does not imagine failing in incrementing the count, and it will be the most troublesome to the other clients, because critical sessions of the other clients will be discounted.

Abby
Ah, it's you, Tony!

Special-Student-7
Anyway, any UNO client should enter any critical block only after the "incrementCriticalSessionCount" method succeeds and should unfailingly call the "decrementCriticalSessionCount" method whenever it leaves any critical block.


5: A Workable Sample


Special-Student-7
Here is a workable sample.

'officeWindowsService' is the main project, here is how to build any project cited in this site, and this article includes some additional information for registering a Windows service.

Before we register the office Windows service, let us test it by executing the office Windows service program on a terminal. Note that although you might want to pass testing, this is required in order to copy the used libraries to the office Windows service program directory.

This is the testing command with the current directory positioned at the office Windows service project directory and the office directory path and the port number adjusted to our environment.

@cmd Source Code
gradle i_executeCsharpExecutableFileTask -Pi_commandLineArguments="\"%the office directory path%\program\" \"socket,host=localhost,port=%the port number%,tcpNoDelay=1;urp;StarOffice.ComponentContext\" \"false\""

If the message, '### Press any key to stop this Windows service.', is shown, the program should have successfully started up. If the program is stopped without any error after a key is pressed, the program should have successfully terminated.

In preparation for registering the office Windows service, an system environment variable, 'URE_BOOTSTRAP', has to be set at the URL of the 'fundamental.ini' file of LibreOffice or Apache OpenOffice (for example, 'file:///D:/libreOffice/program/fundamentalrc.ini').

Now, this is my command to register the Windows service where '%~%'s have to be replaced with the actual values.

@cmd Source Code
sc create OfficeWindowsService start=demand binpath="%the development directory path%\officeWindowsService\target\theBiasPlanet.officeWindowsService.cs.exe %the office directory path%\program socket,host=localhost,port=%the port number%,tcpNoDelay=1;urp;StarOffice.ComponentContext false" obj=%the computer name%\%the operating system user name% password=%the operating system user password%

The specified port has to be opened as described in a previous article, of course.

In fact, the archive file includes also a sample critical-sessions-aware UNO client in Java, C++, C#, or Python.

They can be executed like these with the current directory at 'criticalSessionsAwareExternalUnoClients'.

For Java:

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

For C++:

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

For C#:

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

For Python:

@bash or CMD Source Code
gradle i_executePythonExecutableFileTask -Pi_mainModuleName=theBiasPlanet.criticalSessionsAwareExternalUnoClients.programs.CriticalSessionsAwareExternalUnoClientConsoleProgram -Pi_commandLineArguments="\"socket,host=localhost,port=2002,tcpNoDelay=1;urp;StarOffice.ComponentContext\""

Note that the samples are from my code base, so they use my utility classes in my utility projects, which include much code that is not directly related with those samples; someone may not be happy about such unrelated code included, but please understand that it is not so easy to excise only such unrelated code from the interwoven whole.

The workable sample code is not written as flatly as the code cited in this article (which is written that flat for explanation's sake), but what are done are the same.


References


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