Skip to content

Commit f30c3b9

Browse files
authored
Merge pull request #5287 from JabRef/fixOpenOffice
Try to fix LibreOffice class loader
2 parents 381f0f3 + e62af1f commit f30c3b9

File tree

5 files changed

+402
-57
lines changed

5 files changed

+402
-57
lines changed

CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,8 @@ We refer to [GitHub issues](https://github.com/JabRef/jabref/issues) by using `#
1818
### Fixed
1919

2020
- Inherit fields from cross-referenced entries as specified by biblatex [#5045](https://github.com/JabRef/jabref/issues/5045)
21+
- We fixed an issue where it was no longer possible to connect to LibreOffice [#5261](https://github.com/JabRef/jabref/issues/5261)
22+
2123

2224
### Removed
2325

Lines changed: 382 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,382 @@
1+
// -*- Mode: Java; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*-
2+
3+
/*
4+
* This file is part of the LibreOffice project.
5+
*
6+
* This Source Code Form is subject to the terms of the Mozilla Public
7+
* License, v. 2.0. If a copy of the MPL was not distributed with this
8+
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
9+
*
10+
* This file incorporates work covered by the following license notice:
11+
*
12+
* Licensed to the Apache Software Foundation (ASF) under one or more
13+
* contributor license agreements. See the NOTICE file distributed
14+
* with this work for additional information regarding copyright
15+
* ownership. The ASF licenses this file to you under the Apache
16+
* License, Version 2.0 (the "License"); you may not use this file
17+
* except in compliance with the License. You may obtain a copy of
18+
* the License at http://www.apache.org/licenses/LICENSE-2.0 .
19+
*/
20+
21+
package org.jabref.gui.openoffice;
22+
23+
import java.io.BufferedReader;
24+
import java.io.File;
25+
import java.io.InputStream;
26+
import java.io.InputStreamReader;
27+
import java.io.PrintStream;
28+
import java.io.UnsupportedEncodingException;
29+
import java.net.URLClassLoader;
30+
import java.util.HashMap;
31+
import java.util.Hashtable;
32+
import java.util.Map;
33+
import java.util.Random;
34+
35+
import com.sun.star.bridge.UnoUrlResolver;
36+
import com.sun.star.bridge.XUnoUrlResolver;
37+
import com.sun.star.comp.helper.BootstrapException;
38+
import com.sun.star.comp.helper.ComponentContext;
39+
import com.sun.star.comp.helper.ComponentContextEntry;
40+
import com.sun.star.comp.loader.JavaLoader;
41+
import com.sun.star.comp.servicemanager.ServiceManager;
42+
import com.sun.star.container.XSet;
43+
import com.sun.star.lang.XInitialization;
44+
import com.sun.star.lang.XMultiComponentFactory;
45+
import com.sun.star.lang.XMultiServiceFactory;
46+
import com.sun.star.lib.util.NativeLibraryLoader;
47+
import com.sun.star.loader.XImplementationLoader;
48+
import com.sun.star.uno.UnoRuntime;
49+
import com.sun.star.uno.XComponentContext;
50+
51+
/** Bootstrap offers functionality to obtain a context or simply
52+
a service manager.
53+
The service manager can create a few basic services, whose implementations are:
54+
<ul>
55+
<li>com.sun.star.comp.loader.JavaLoader</li>
56+
<li>com.sun.star.comp.urlresolver.UrlResolver</li>
57+
<li>com.sun.star.comp.bridgefactory.BridgeFactory</li>
58+
<li>com.sun.star.comp.connections.Connector</li>
59+
<li>com.sun.star.comp.connections.Acceptor</li>
60+
<li>com.sun.star.comp.servicemanager.ServiceManager</li>
61+
</ul>
62+
63+
Other services can be inserted into the service manager by
64+
using its XSet interface:
65+
<pre>
66+
XSet xSet = UnoRuntime.queryInterface( XSet.class, aMultiComponentFactory );
67+
// insert the service manager
68+
xSet.insert( aSingleComponentFactory );
69+
</pre>
70+
*/
71+
public class Bootstrap {
72+
73+
private static final Random RANDOM_PIPE_NAME = new Random();
74+
private static boolean M_LOADED_JUH = false;
75+
76+
private static void insertBasicFactories(XSet xSet, XImplementationLoader xImpLoader) throws Exception {
77+
// insert the factory of the loader
78+
xSet.insert(xImpLoader.activate("com.sun.star.comp.loader.JavaLoader", null, null, null));
79+
80+
// insert the factory of the URLResolver
81+
xSet.insert(xImpLoader.activate("com.sun.star.comp.urlresolver.UrlResolver", null, null, null));
82+
83+
// insert the bridgefactory
84+
xSet.insert(xImpLoader.activate("com.sun.star.comp.bridgefactory.BridgeFactory", null, null, null));
85+
86+
// insert the connector
87+
xSet.insert(xImpLoader.activate("com.sun.star.comp.connections.Connector", null, null, null));
88+
89+
// insert the acceptor
90+
xSet.insert(xImpLoader.activate("com.sun.star.comp.connections.Acceptor", null, null, null));
91+
}
92+
93+
/**
94+
* Returns an array of default commandline options to start bootstrapped
95+
* instance of soffice with. You may use it in connection with bootstrap
96+
* method for example like this:
97+
* <pre>
98+
* List list = Arrays.asList( Bootstrap.getDefaultOptions() );
99+
* list.remove("--nologo");
100+
* list.remove("--nodefault");
101+
* list.add("--invisible");
102+
*
103+
* Bootstrap.bootstrap( list.toArray( new String[list.size()] );
104+
* </pre>
105+
*
106+
* @return an array of default commandline options
107+
* @see #bootstrap( String[] )
108+
* @since LibreOffice 5.1
109+
*/
110+
public static final String[] getDefaultOptions() {
111+
return new String[] {"--nologo", "--nodefault", "--norestore", "--nolockcheck"};
112+
}
113+
114+
/**
115+
backwards compatibility stub.
116+
@param context_entries the hash table contains mappings of entry names (type string) to
117+
context entries (type class ComponentContextEntry).
118+
@throws Exception if things go awry.
119+
@return a new context.
120+
*/
121+
public static XComponentContext createInitialComponentContext(Hashtable<String, Object> context_entries) throws Exception {
122+
return createInitialComponentContext((Map<String, Object>) context_entries);
123+
}
124+
125+
/** Bootstraps an initial component context with service manager and basic
126+
jurt components inserted.
127+
@param context_entries the hash table contains mappings of entry names (type string) to
128+
context entries (type class ComponentContextEntry).
129+
@throws Exception if things go awry.
130+
@return a new context.
131+
*/
132+
public static XComponentContext createInitialComponentContext(Map<String, Object> context_entries) throws Exception {
133+
ServiceManager xSMgr = new ServiceManager();
134+
135+
XImplementationLoader xImpLoader = UnoRuntime.queryInterface(XImplementationLoader.class, new JavaLoader());
136+
XInitialization xInit = UnoRuntime.queryInterface(XInitialization.class, xImpLoader);
137+
Object[] args = new Object[] {xSMgr};
138+
xInit.initialize(args);
139+
140+
// initial component context
141+
if (context_entries == null) {
142+
context_entries = new HashMap<>(1);
143+
}
144+
// add smgr
145+
context_entries.put("/singletons/com.sun.star.lang.theServiceManager", new ComponentContextEntry(null, xSMgr));
146+
// ... xxx todo: add standard entries
147+
XComponentContext xContext = new ComponentContext(context_entries, null);
148+
149+
xSMgr.setDefaultContext(xContext);
150+
151+
XSet xSet = UnoRuntime.queryInterface(XSet.class, xSMgr);
152+
// insert basic jurt factories
153+
insertBasicFactories(xSet, xImpLoader);
154+
155+
return xContext;
156+
}
157+
158+
/**
159+
* Bootstraps a servicemanager with the jurt base components registered.
160+
*
161+
* See also UNOIDL <code>com.sun.star.lang.ServiceManager</code>.
162+
*
163+
* @throws Exception if things go awry.
164+
* @return a freshly bootstrapped service manager
165+
*/
166+
public static XMultiServiceFactory createSimpleServiceManager() throws Exception {
167+
return UnoRuntime.queryInterface(XMultiServiceFactory.class, createInitialComponentContext((Map<String, Object>) null).getServiceManager());
168+
}
169+
170+
/** Bootstraps the initial component context from a native UNO installation.
171+
172+
@throws Exception if things go awry.
173+
@return a freshly bootstrapped component context.
174+
175+
See also
176+
<code>cppuhelper/defaultBootstrap_InitialComponentContext()</code>.
177+
*/
178+
public static final XComponentContext defaultBootstrap_InitialComponentContext() throws Exception {
179+
return defaultBootstrap_InitialComponentContext((String) null, (Map<String, String>) null);
180+
}
181+
182+
/**
183+
* Backwards compatibility stub.
184+
*
185+
* @param ini_file
186+
* ini_file (may be null: uno.rc besides cppuhelper lib)
187+
* @param bootstrap_parameters
188+
* bootstrap parameters (maybe null)
189+
*
190+
* @throws Exception if things go awry.
191+
* @return a freshly bootstrapped component context.
192+
*/
193+
public static final XComponentContext defaultBootstrap_InitialComponentContext(String ini_file, Hashtable<String, String> bootstrap_parameters) throws Exception {
194+
return defaultBootstrap_InitialComponentContext(ini_file, (Map<String, String>) bootstrap_parameters);
195+
}
196+
197+
/** Bootstraps the initial component context from a native UNO installation.
198+
199+
See also
200+
<code>cppuhelper/defaultBootstrap_InitialComponentContext()</code>.
201+
202+
@param ini_file
203+
ini_file (may be null: uno.rc besides cppuhelper lib)
204+
@param bootstrap_parameters
205+
bootstrap parameters (maybe null)
206+
207+
@throws Exception if things go awry.
208+
@return a freshly bootstrapped component context.
209+
*/
210+
public static final XComponentContext defaultBootstrap_InitialComponentContext(String ini_file, Map<String, String> bootstrap_parameters) throws Exception {
211+
// jni convenience: easier to iterate over array than calling Hashtable
212+
String pairs[] = null;
213+
if (null != bootstrap_parameters) {
214+
pairs = new String[2 * bootstrap_parameters.size()];
215+
int n = 0;
216+
for (Map.Entry<String, String> bootstrap_parameter : bootstrap_parameters.entrySet()) {
217+
pairs[n++] = bootstrap_parameter.getKey();
218+
pairs[n++] = bootstrap_parameter.getValue();
219+
}
220+
}
221+
222+
if (!M_LOADED_JUH) {
223+
if ("The Android Project".equals(System.getProperty("java.vendor"))) {
224+
// Find out if we are configured with DISABLE_DYNLOADING or
225+
// not. Try to load the lo-bootstrap shared library which
226+
// won't exist in the DISABLE_DYNLOADING case. (And which will
227+
// be already loaded otherwise, so nothing unexpected happens
228+
// that case.) Yeah, this would be simpler if I just could be
229+
// bothered to keep a separate branch for DISABLE_DYNLOADING
230+
// on Android, merging in master periodically, until I know
231+
// for sure whether it is what I want, or not.
232+
233+
boolean disable_dynloading = false;
234+
try {
235+
System.loadLibrary("lo-bootstrap");
236+
} catch (UnsatisfiedLinkError e) {
237+
disable_dynloading = true;
238+
}
239+
240+
if (!disable_dynloading) {
241+
NativeLibraryLoader.loadLibrary(Bootstrap.class.getClassLoader(), "juh");
242+
}
243+
} else {
244+
NativeLibraryLoader.loadLibrary(Bootstrap.class.getClassLoader(), "juh");
245+
}
246+
M_LOADED_JUH = true;
247+
}
248+
return UnoRuntime.queryInterface(XComponentContext.class, cppuhelper_bootstrap(ini_file, pairs, Bootstrap.class.getClassLoader()));
249+
}
250+
251+
private static native Object cppuhelper_bootstrap(String ini_file, String bootstrap_parameters[], ClassLoader loader) throws Exception;
252+
253+
/**
254+
* Bootstraps the component context from a UNO installation.
255+
*
256+
* @throws BootstrapException if things go awry.
257+
*
258+
* @return a bootstrapped component context.
259+
*
260+
* @since UDK 3.1.0
261+
*/
262+
public static final XComponentContext bootstrap(URLClassLoader loader) throws BootstrapException {
263+
264+
String[] defaultArgArray = getDefaultOptions();
265+
return bootstrap(defaultArgArray, loader);
266+
}
267+
268+
/**
269+
* Bootstraps the component context from a UNO installation.
270+
*
271+
* @param argArray
272+
* an array of strings - commandline options to start instance of
273+
* soffice with
274+
* @see #getDefaultOptions()
275+
*
276+
* @throws BootstrapException if things go awry.
277+
*
278+
* @return a bootstrapped component context.
279+
*
280+
* @since LibreOffice 5.1
281+
*/
282+
public static final XComponentContext bootstrap(String[] argArray, URLClassLoader loader) throws BootstrapException {
283+
284+
XComponentContext xContext = null;
285+
286+
try {
287+
// create default local component context
288+
XComponentContext xLocalContext = createInitialComponentContext((Map<String, Object>) null);
289+
if (xLocalContext == null) {
290+
throw new BootstrapException("no local component context!");
291+
}
292+
293+
// find office executable relative to this class's class loader
294+
String sOffice = System.getProperty("os.name").startsWith("Windows") ? "soffice.exe" : "soffice";
295+
296+
File fOffice = NativeLibraryLoader.getResource(loader, sOffice);
297+
if (fOffice == null) {
298+
throw new BootstrapException("no office executable found!");
299+
}
300+
301+
// create call with arguments
302+
//We need a socket, pipe does not work. https://api.libreoffice.org/examples/examples.html
303+
String[] cmdArray = new String[argArray.length + 2];
304+
cmdArray[0] = fOffice.getPath();
305+
cmdArray[1] = ("--accept=socket,host=localhost,port=2083" + ";urp;");
306+
307+
System.arraycopy(argArray, 0, cmdArray, 2, argArray.length);
308+
309+
// start office process
310+
Process p = Runtime.getRuntime().exec(cmdArray);
311+
pipe(p.getInputStream(), System.out, "CO> ");
312+
pipe(p.getErrorStream(), System.err, "CE> ");
313+
314+
// initial service manager
315+
XMultiComponentFactory xLocalServiceManager = xLocalContext.getServiceManager();
316+
if (xLocalServiceManager == null) {
317+
throw new BootstrapException("no initial service manager!");
318+
}
319+
320+
// create a URL resolver
321+
XUnoUrlResolver xUrlResolver = UnoUrlResolver.create(xLocalContext);
322+
323+
// connection string
324+
String sConnect = "uno:socket,host=localhost,port=2083" + ";urp;StarOffice.ComponentContext";
325+
326+
// wait until office is started
327+
for (int i = 0;; ++i) {
328+
try {
329+
// try to connect to office
330+
Object context = xUrlResolver.resolve(sConnect);
331+
xContext = UnoRuntime.queryInterface(XComponentContext.class, context);
332+
if (xContext == null) {
333+
throw new BootstrapException("no component context!");
334+
}
335+
break;
336+
} catch (com.sun.star.connection.NoConnectException ex) {
337+
// Wait 500 ms, then try to connect again, but do not wait
338+
// longer than 5 min (= 600 * 500 ms) total:
339+
if (i == 600) {
340+
throw new BootstrapException(ex);
341+
}
342+
Thread.sleep(500);
343+
}
344+
}
345+
} catch (BootstrapException e) {
346+
throw e;
347+
} catch (java.lang.RuntimeException e) {
348+
throw e;
349+
} catch (java.lang.Exception e) {
350+
throw new BootstrapException(e);
351+
}
352+
353+
return xContext;
354+
}
355+
356+
private static void pipe(final InputStream in, final PrintStream out, final String prefix) {
357+
358+
new Thread("Pipe: " + prefix) {
359+
360+
@Override
361+
public void run() {
362+
try {
363+
BufferedReader r = new BufferedReader(new InputStreamReader(in, "UTF-8"));
364+
365+
for (;;) {
366+
String s = r.readLine();
367+
if (s == null) {
368+
break;
369+
}
370+
out.println(prefix + s);
371+
}
372+
} catch (UnsupportedEncodingException e) {
373+
e.printStackTrace(System.err);
374+
} catch (java.io.IOException e) {
375+
e.printStackTrace(System.err);
376+
}
377+
}
378+
}.start();
379+
}
380+
}
381+
382+
// vim:set shiftwidth=4 softtabstop=4 expandtab:

0 commit comments

Comments
 (0)