<The previous article in this series | The table of contents of this series |
This Series Is a Complement to the Main Series, 'To Develop UNO Extensions (LibreOffice Extensions or Apache OpenOffice Extensions)', for Applying Descriptions of the Main Series to Using UNO in External Java Programs
About: UNO (Universal Network Objects)
About: LibreOffice
About: Apache OpenOffice
About: Java Programming Language
There is already a series, 'To Develop UNO Extensions (LibreOffice Extensions or Apache OpenOffice Extensions)', which is about developing UNO extensions that consist of Java UNO components, and most of its descriptions also apply to developing external UNO programs because they are about using UNO, which is the same whether we use it in UNO extensions or external programs.
What do you mean by 'external programs'?
By 'external programs', I mean programs that run outside any LibreOffice program, typically ordinary programs that start with the 'main' methods (I'm talking only about Java programs).
That isn't an accurate explanation of 'external programs', is it?
Well, . . . it isn't. . . . Hmm, a simple explanation like "External programs are standalone programs." will be liked and will be thought to be an explanation that is 'easy to understand'; explaining more accurately is likely to be hated, or more likely, just to be thrown out immediately, by most people.
I don't mind.
Well, as I'm talking to you, not to most people, it certainly is fine if you don't mind. . . .
To be exact, being a external program is being any environment to which the desired component context (the 'component context' is a handle to a UNO environment) is not just given.
Ah-ha . . .
Do you understand?
Actually, I do.
That's good. To give a supplementary explanation, once we get the component context of the UNO environment that we desire to handle, we can do whatever allowed us to do to the UNO environment; so, how to get the component context is the issue.
For a UNO extension, the component context of the UNO environment into which the UNO extension has been registered is passed into the UNO extension, but for an 'ordinary' Java program, any component context won't be passed into it miraculously, perhaps, from Heaven.
So, we have to go to get the desired component context ourselves. In other words, if a program is in that situation, that program is an external program, whether it is an 'ordinary' program, a web application, an applet, or whatever, even a UNO extension.
If the UNO extension wants to access a UNO environment that isn't the UNO environment into which the UNO extension has been registered, it is an external program to the desired UNO environment.
If the truth be told, just saying 'external' is nonsense: I would say, "External to what?"
Obviously, what we mean is 'external' to the desired UNO environment.
To repeat, all the descriptions of the main series about after the component context is obtained are completely applicable to any 'external program'. For example, the descriptions about reading from and writing to spread sheet cells are completely applicable to external UNO programs: just use the appropriate component context.
As we don't want to make duplicate descriptions, in this series, we will describe only matters that are solely about external UNO programs.
OK.
First, we have to build the environment for developing external UNO programs. As the environment for developing external UNO programs is almost the same with that for developing UNO extensions, see the corresponding articles (here for Linux and here for Windows) in the main series.
What are the differences between the environment for developing external UNO programs and the environment for developing UNO extensions?
The environment for developing external UNO programs is a sub set of the environment for developing UNO extensions: there are some things unnecessary for developing external UNO programs.
For Linux, wmctrl is unnecessary because we don't need to shutdown LibreOffice program instances. And for both Linux and Windows, LibreOffice SDK isn't necessarily necessary if we develop only UNO clients (not servers) although it is handy because it includes some documents.
We needed it for developing UNO extensions because it includes commands like 'idlc', which compiles UNOIDL files (see here to know what UNOIDL files are).
Yes. We wouldn't have to create UNO components (see here to know what UNO components are) although we can, if we are going to create only UNO clients.
So, the Jar files we use are included in LibreOffice itself, not in the SDK?
Yes.
And if we want to use other building tools like Maven, make, Eclipse, NetBeans, or whatever, of course, we can. For developing external UNO clients, it's just a matter of including necessary Jar files in the classpath.
Although we have implemented the functionality of compiling UNOIDL files, creating the UNO data types merged registry file, creating the UNO extension file, registering the UNO extension into LibreOffice, and restarting the LibreOffice program instance, into our Gradle build scripts and Ant build files, that functionality isn't necessary for developing external UNO clients.
For a case in which we just want to get the component context of the UNO environment of a local host LibreOffice program instance, there is a simple way like this (just make sure that as this uses JNI, java.library.path must be properly set at run time, or an exception, "java.lang.UnsatisfiedLinkError: no jpipe in java.library.path", will occur).
import com.sun.star.uno.XComponentContext;
import com.sun.star.comp.helper.Bootstrap;
XComponentContext l_remoteComponentContextInXComponentContext = Bootstrap.bootstrap ();
That's it?
That's it. Now, we get the component context, and can apply most of the descriptions in the series, 'To Develop UNO Extensions (LibreOffice Extensions or Apache OpenOffice Extensions)', to our external UNO programs, just by using the component context.
But the LibreOffice program instance to be connected to must be on the local host, meaning being in the same computer with our external UNO programs, right?
Yup. Actually, that method uses a named pipe to connect to the LibreOffice program instance.
How will the name be determined?
The method creates a random name every time called, registers the name to the LibreOffice program instance (if no LibreOffice program instance is up, the method will start one).
So, if we run our external UNO program many times while the LibreOffice program instance keeps up, will many names be accumulated in the LibreOffice program instance?
That seems to be the case. Certainly, there will be a limit on the number of named pipes, but . . .
Well, practically, there may not be any harm in most cases, but it seems a waste.
Anyway, if one is happy with that way, he or she doesn't need to read along this series further: the rest to be known is (or will be) written in the main series, 'To Develop UNO Extensions (LibreOffice Extensions or Apache OpenOffice Extensions)'.
If we want to connect to a remote host LibreOffice program instance, want to detect disconnections, or want to disclose UNO objects from the external UNO program, we will have to use another way.
As this way can't be written in one line, I have created utility classes, which can be downloaded from here.
Deferring the lengthy lecture to future articles, we can get the component context of a LibreOffice program instance like this, where we pass 'socket,host=localhost,port=2002,tcpNoDelay=1;urp;StarOffice.ComponentContext' to 'a_serverUrl'.
package test.unobatchclienttest1;
import java.time.LocalDateTime;
import java.math.BigDecimal;
import com.sun.star.uno.XComponentContext;
import thebiasplanet.unoutilities.programshandling.UnoEnvironment;
import thebiasplanet.unoutilities.connectionshandling.UnoConnectionConnector;
import thebiasplanet.unoutilities.connectionshandling.UnoConnection;
final public class Test1Test {
public static void test (String a_serverUrl, String a_spreadSheetsDocumentFileUrl) {
UnoEnvironment l_unoEnvironment = null;
UnoConnection l_unoConnection = null;
try {
l_unoEnvironment = new UnoEnvironment (null, LocalDateTime.now ().toString (), null);
UnoConnectionConnector l_unoConnectionConnector = new UnoConnectionConnector (l_unoEnvironment.getLocalComponentContext ());
l_unoConnection = l_unoConnectionConnector.connect (a_serverUrl, null);
XComponentContext l_remoteComponentContextInXComponentContext = l_unoConnection.getRemoteComponentContextInXComponentContext ();
}
catch (Exception l_exception) {
System.out.println (l_exception.toString ());
}
finally {
if (l_unoConnection != null) {
l_unoConnection.disconnect ();
}
}
}
}
So, we use a socket, not a named pipe, and if we want to connect to a remote host LibreOffice program instance, we can just change the 'localhost' part.
The LibreOffice program instance has to have been started like this.
For Linux:
soffice --accept=socket,host=localhost,port=2002\;urp\;
For Windows:
soffice --accept=socket,host=localhost,port=2002;urp;
Or we can add a parameter, '--headless', if we want the LibreOffice program instance to be headless (meaning invisible, mostly).
That starts a LibreOffice program instance, opening the port, '2002'. . . . What if a LibreOffice program instance is already up without the port opened?
The exactly same command opens the port with the instance kept up.
I see.
In fact, as the 'thebiasplanet.unoutilities.jar' (a Jar file built from one of the downloaded projects) includes utility classes to access spread sheets, we can read from and write to spread sheet cells like this (inserting these import directives and the subsequent code into appropriate places of the above code respectively), while we pass the URL of a spread sheets document file (the file has to have a sheet named 'Sheet1') to 'a_spreadSheetsDocumentFileUrl'.
import thebiasplanet.unoutilities.spreadsheetshandling.UnoSpreadSheetsDocument;
import thebiasplanet.unoutilities.spreadsheetshandling.UnoSpreadSheet;
UnoSpreadSheetsDocument l_spreadSheetsDocument = UnoSpreadSheetsDocument.openSpreadSheetsDocumentFile (l_remoteComponentContextInXComponentContext, a_spreadSheetsDocumentFileUrl, true);
try {
UnoSpreadSheet l_spreadSheet = l_spreadSheetsDocument.getSpreadSheet ("Sheet1");
Object l_cellValue = l_spreadSheet.getSpreadSheetCell (0, 0).getValue ();
System.out.println (String.format ("The cell value at the 0, 0 cell is %s.", l_cellValue.toString ()));
l_spreadSheet.getSpreadSheetCell (1, 0).setValue (new BigDecimal ("12.340"));
l_spreadSheetsDocument.store ();
}
catch (Exception l_exception) {
System.out.println (l_exception.toString ());
}
finally {
l_spreadSheetsDocument.close ();
}
Those utility classes are things that gathered together what are described in some articles of the main series (especially this and several subsequent articles).
Yes.
Actually, the test program is included in the above zip file, and we can build the utilities Jars (in fact, there are two, 'thebiasplanet.coreutilities.jar' and 'thebiasplanet.unoutilities.jar') and the test program, and run the test program like this.
1. Expand the zip file with the directories structure preserved.
2. May have to change some parameters ('LIBREOFFICE_DIRECTORY_NAME' and 'LIBREOFFICE_SDK_DIRECTORY_NAME') in 'commonBuild01.gradle' or 'commonBuild.properties' (see here).
3. On a terminal, change the current directory to the 'coreUtilitiesToDisclose', and execute 'gradle' or 'ant'.
4. Change the current directory to the 'unoUtilitiesToDisclose', and execute 'gradle' or 'ant'.
5. In 'unoUtilitiesTestToDisclose', make sure that the contents of the main method of 'Test.java' is this.
test.unobatchclienttest1.Test1Test.test (a_arguments [0], a_arguments [1]);
6. Change the current directory to the 'unoUtilitiesTestToDisclose', and execute 'gradle' or 'ant'.
7. For Gradle on Linux bash, execute this (change the '$(pwd)' part for other environments, especially '%CD%' for Windows).
gradle test -PCOMMAND_LINE_ARGUMENTS="socket,host=localhost,port=2002,tcpNoDelay=1;urp;StarOffice.ComponentContext file://$(pwd)/execution/TestSpreadSheetsDocument1.ods"
For Ant on Linux bash, execute this (change the '$(pwd)' part for other environments, especially '%CD%' for Windows).
ant test -DCOMMAND_LINE_ARGUMENTS="socket,host=localhost,port=2002,tcpNoDelay=1;urp;StarOffice.ComponentContext file://$(pwd)/execution/TestSpreadSheetsDocument1.ods"
I have briefly stated merits of the latter way, but I will elaborate on them.
OK.
We can connect to a LibreOffice program instance on a remote host if the instance has opened a port.
All right.
We can register listeners of disconnection events.
Although we may not feel any necessity to register such listeners for batch-like client programs as the sample above, when we create a GUI client that stays alive for indefinite periods, detecting such events can be handy.
So, when the connection is broken, perhaps because the LibreOffice program instance has been shutdown normally or abnormally, or the network has been severed, the client detects the event immediately.
Yes.
Well, it sounds slick, but I don't particularly see how that functionality is necessary.
Well, you mean, detecting the disconnection when trying to access the remote UNO environment is enough?
Yes, at least for most cases.
That may be so. . . . We can just appropriately handle errors caused by disconnections. In fact, I haven't felt any particular inconvenience that way regarding, for example, JDBC connections. And if necessary, we can do polling.
. . .
As another merit, we can create UNO servers that disclose UNO objects.
Ah-ha. So, for example, we can create a UNO server that does some intensive calculations or data accesses, and let LibreOffice program instances delegate such intensive work to the server through, perhaps, UNO extensions or Basic macros.
Yes. Then, we don't need to do intensive work by Basic macros, which don't seem to be optimal for such work.
Can you give me a more specific guidance of what in the main series are or are not applicable to external UNO programs?
Well, if we are concerned with developing only external UNO clients (not servers), most of the articles about the two sample UNO extensions will be irrelevant. On the other hand, if we are also concerned with developing external UNO servers, also most of those articles are relevant because we are likely to create UNO components and register them as UNO services.
What about other articles?
They are about generally using UNO. So, They are the same for external UNO programs.
Especially, correct understanding of basic concepts of UNO will be necessary in order to use UNO at will. Without that understanding, we wouldn't be able to sufficiently understand what the UNO API documents say.
<The previous article in this series | The table of contents of this series |