1 /*************************************************************************
3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
5 * Copyright 2008 by Sun Microsystems, Inc.
7 * OpenOffice.org - a multi-platform office productivity suite
9 * $RCSfile: LocalOfficeConnection.java,v $
12 * This file is part of OpenOffice.org.
14 * OpenOffice.org is free software: you can redistribute it and/or modify
15 * it under the terms of the GNU Lesser General Public License version 3
16 * only, as published by the Free Software Foundation.
18 * OpenOffice.org is distributed in the hope that it will be useful,
19 * but WITHOUT ANY WARRANTY; without even the implied warranty of
20 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21 * GNU Lesser General Public License version 3 for more details
22 * (a copy is included in the LICENSE file that accompanied this code).
24 * You should have received a copy of the GNU Lesser General Public License
25 * version 3 along with OpenOffice.org. If not, see
26 * <http://www.openoffice.org/license.html>
27 * for a copy of the LGPLv3 License.
29 ************************************************************************/
31 package com
.sun
.star
.comp
.beans
;
33 import java
.awt
.Container
;
35 import java
.util
.Iterator
;
36 import java
.util
.List
;
37 import java
.util
.Vector
;
39 import com
.sun
.star
.lang
.XMultiComponentFactory
;
40 import com
.sun
.star
.lang
.XComponent
;
41 import com
.sun
.star
.lang
.XEventListener
;
42 import com
.sun
.star
.connection
.XConnection
;
43 import com
.sun
.star
.connection
.XConnector
;
44 import com
.sun
.star
.bridge
.XBridge
;
45 import com
.sun
.star
.bridge
.XBridgeFactory
;
46 import com
.sun
.star
.beans
.XPropertySet
;
47 import com
.sun
.star
.uno
.XComponentContext
;
48 import com
.sun
.star
.uno
.UnoRuntime
;
49 import com
.sun
.star
.uno
.Exception
;
50 import com
.sun
.star
.lib
.uno
.helper
.UnoUrl
;
51 import com
.sun
.star
.lib
.util
.NativeLibraryLoader
;
54 * This class reprecents a connection to the local office application.
58 public class LocalOfficeConnection
59 implements OfficeConnection
61 public static final String OFFICE_APP_NAME
= "soffice";
62 public static final String OFFICE_LIB_NAME
= "officebean";
63 public static final String OFFICE_ID_SUFFIX
= "_Office";
65 private static String mProgramPath
;
67 private Process mProcess
;
68 private ContainerFactory mContainerFactory
;
69 private XComponentContext mContext
;
70 private XBridge mBridge
;
73 private String mConnType
;
76 private String mProtocol
;
77 private String mInitialObject
;
79 private List mComponents
= new Vector();
81 private static long m_nBridgeCounter
= 0;
82 //-------------------------------------------------------------------------
85 // preload shared libraries whichs import lips are linked to officebean
86 if ( System
.getProperty( "os.name" ).startsWith( "Windows" ) )
90 NativeLibraryLoader
.loadLibrary(LocalOfficeConnection
.class.getClassLoader(), "msvcr70");
94 // loading twice would fail
95 System
.err
.println( "cannot find msvcr70" );
100 NativeLibraryLoader
.loadLibrary(LocalOfficeConnection
.class.getClassLoader(), "msvcr71");
104 // loading twice would fail
105 System
.err
.println( "cannot find msvcr71" );
110 NativeLibraryLoader
.loadLibrary(LocalOfficeConnection
.class.getClassLoader(), "uwinapi");
114 // loading twice would fail
115 System
.err
.println( "cannot find uwinapi" );
120 NativeLibraryLoader
.loadLibrary(LocalOfficeConnection
.class.getClassLoader(), "jawt");
124 // loading twice would fail
125 System
.err
.println( "cannot find jawt" );
129 // load shared library for JNI code
130 NativeLibraryLoader
.loadLibrary( LocalOfficeConnection
.class.getClassLoader(), "officebean" );
133 //-------------------------------------------------------------------------
135 private void dbgPrint( String aMessage
)
137 System
.err
.println( aMessage
);
142 * Sets up paths to the office application and native libraries if
143 * values are available in <code>OFFICE_PROP_FILE</code> in the user
144 * home directory.<br />
145 * "com.sun.star.beans.path" - the office application directory;<br/>
146 * "com.sun.star.beans.libpath" - native libraries directory.
148 public LocalOfficeConnection()
153 setUnoUrl( "uno:pipe,name=" + getPipeName() + ";urp;StarOffice.ServiceManager" );
155 catch ( java
.net
.MalformedURLException e
)
160 * Sets a connection URL.
161 * This implementation accepts a UNO URL with following format:<br />
163 * url := uno:localoffice[,<params>];urp;StarOffice.ServiceManager
164 * params := <path>[,<pipe>]
165 * path := path=<pathv>
166 * pipe := pipe=<pipev>
167 * pathv := platform_specific_path_to_the_local_office_distribution
168 * pipev := local_office_connection_pipe_name
171 * @param url This is UNO URL which discribes the type of a connection.
173 public void setUnoUrl(String url
)
174 throws java
.net
.MalformedURLException
178 String prefix
= "uno:localoffice";
179 if ( url
.startsWith(prefix
) )
180 parseUnoUrlWithOfficePath( url
, prefix
);
185 UnoUrl aURL
= UnoUrl
.parseUnoUrl( url
);
187 mConnType
= aURL
.getConnection();
188 mPipe
= (String
) aURL
.getConnectionParameters().get( "pipe" );
189 mPort
= (String
) aURL
.getConnectionParameters().get( "port" );
190 mProtocol
= aURL
.getProtocol();
191 mInitialObject
= aURL
.getRootOid();
193 catch ( com
.sun
.star
.lang
.IllegalArgumentException eIll
)
195 throw new java
.net
.MalformedURLException(
196 "Invalid UNO connection URL.");
203 * Sets an AWT container catory.
205 * @param containerFactory This is a application provided AWT container
208 public void setContainerFactory(ContainerFactory containerFactory
)
210 mContainerFactory
= containerFactory
;
214 * Retrives the UNO component context.
215 * Establishes a connection if necessary and initialises the
216 * UNO service manager if it has not already been initialised.
217 * This method can return <code>null</code> if it fails to connect
218 * to the office application.
220 * @return The office UNO component context.
222 synchronized public XComponentContext
getComponentContext()
224 if ( mContext
== null )
225 mContext
= connect();
230 * Creates an office window.
231 * The window is either a sub-class of java.awt.Canvas (local) or
232 * java.awt.Container (RVP).
234 * @param container This is an AWT container.
235 * @return The office window instance.
237 public OfficeWindow
createOfficeWindow(Container container
)
239 return new LocalOfficeWindow(this);
243 * Closes the connection.
245 public void dispose()
247 Iterator itr
= mComponents
.iterator();
248 while (itr
.hasNext() == true) {
249 // ignore runtime exceptions in dispose
250 try { ((XEventListener
)itr
.next()).disposing(null); }
251 catch ( RuntimeException aExc
) {}
255 //Terminate the bridge. It turned out that this is necessary for the bean
256 //to work properly when displayed in an applet within Internet Explorer.
257 //When navigating off the page which is showing the applet and then going
258 //back to it, then the Java remote bridge is damaged. That is the Java threads
259 //do not work properly anymore. Therefore when Applet.stop is called the connection
260 //to the office including the bridge needs to be terminated.
263 XComponent comp
= (XComponent
)UnoRuntime
.queryInterface(
264 XComponent
.class, mBridge
);
268 System
.err
.println("LocalOfficeConnection: could not dispose bridge!");
273 mContainerFactory
= null;
278 * Adds an event listener to the object.
280 * @param listener is a listener object.
282 public void addEventListener(XEventListener listener
)
284 mComponents
.add(listener
);
288 * Removes an event listener from the listener list.
290 * @param listener is a listener object.
292 public void removeEventListener(XEventListener listener
)
294 mComponents
.remove(listener
);
298 * Establishes the connection to the office.
300 private XComponentContext
connect()
304 // create default local component context
305 XComponentContext xLocalContext
=
306 com
.sun
.star
.comp
.helper
.Bootstrap
.createInitialComponentContext(null);
308 // initial serviceManager
309 XMultiComponentFactory xLocalServiceManager
= xLocalContext
.getServiceManager();
311 // try to connect to soffice
312 Object aInitialObject
= null;
315 aInitialObject
= resolve(xLocalContext
, mURL
);
317 catch( com
.sun
.star
.connection
.NoConnectException e
)
320 OfficeService aSOffice
= new OfficeService();
321 aSOffice
.startupService();
323 // wait until soffice is started
324 long nMaxMillis
= System
.currentTimeMillis() + 1000*aSOffice
.getStartupTime();
325 while ( aInitialObject
== null )
329 // try to connect to soffice
330 Thread
.currentThread().sleep( 500 );
331 aInitialObject
= resolve(xLocalContext
, mURL
);
333 catch( com
.sun
.star
.connection
.NoConnectException aEx
)
335 // soffice did not start in time
336 if ( System
.currentTimeMillis() > nMaxMillis
)
347 if( null != aInitialObject
)
349 XPropertySet xPropertySet
= (XPropertySet
)
350 UnoRuntime
.queryInterface( XPropertySet
.class, aInitialObject
);
351 Object xContext
= xPropertySet
.getPropertyValue("DefaultContext");
352 XComponentContext xComponentContext
= (XComponentContext
) UnoRuntime
.queryInterface(
353 XComponentContext
.class, xContext
);
354 return xComponentContext
;
357 catch( com
.sun
.star
.connection
.NoConnectException e
)
359 System
.out
.println( "Couldn't connect to remote server" );
360 System
.out
.println( e
.getMessage() );
362 catch( com
.sun
.star
.connection
.ConnectionSetupException e
)
364 System
.out
.println( "Couldn't access necessary local resource to establish the interprocess connection" );
365 System
.out
.println( e
.getMessage() );
367 catch( com
.sun
.star
.lang
.IllegalArgumentException e
)
369 System
.out
.println( "uno-url is syntactical illegal ( " + mURL
+ " )" );
370 System
.out
.println( e
.getMessage() );
372 catch( com
.sun
.star
.uno
.RuntimeException e
)
374 System
.out
.println( "--- RuntimeException:" );
375 System
.out
.println( e
.getMessage() );
377 System
.out
.println( "--- end." );
380 catch( java
.lang
.Exception e
)
382 System
.out
.println( "java.lang.Exception: " );
383 System
.out
.println( e
);
385 System
.out
.println( "--- end." );
386 throw new com
.sun
.star
.uno
.RuntimeException( e
.toString() );
393 //The function is copied and adapted from the UrlResolver.resolve.
394 //We cannot use the URLResolver because we need access to the bridge which has
395 //to be disposed when Applet.stop is called.
396 private Object
resolve(XComponentContext xLocalContext
, String dcp
)
397 throws com
.sun
.star
.connection
.NoConnectException
,
398 com
.sun
.star
.connection
.ConnectionSetupException
,
399 com
.sun
.star
.lang
.IllegalArgumentException
401 String conDcp
= null;
402 String protDcp
= null;
403 String rootOid
= null;
405 if(dcp
.indexOf(';') == -1) {// use old style
408 rootOid
= "classic_uno";
411 int index
= dcp
.indexOf(':');
412 String url
= dcp
.substring(0, index
).trim();
413 dcp
= dcp
.substring(index
+ 1).trim();
415 index
= dcp
.indexOf(';');
416 conDcp
= dcp
.substring(0, index
).trim();
417 dcp
= dcp
.substring(index
+ 1).trim();
419 index
= dcp
.indexOf(';');
420 protDcp
= dcp
.substring(0, index
).trim();
421 dcp
= dcp
.substring(index
+ 1).trim();
423 rootOid
= dcp
.trim().trim();
426 Object rootObject
= null;
427 XBridgeFactory xBridgeFactory
= null;
429 XMultiComponentFactory xLocalServiceManager
= xLocalContext
.getServiceManager();
431 xBridgeFactory
= (XBridgeFactory
)UnoRuntime
.queryInterface(
432 XBridgeFactory
.class,
433 xLocalServiceManager
.createInstanceWithContext(
434 "com.sun.star.bridge.BridgeFactory", xLocalContext
));
435 } catch (com
.sun
.star
.uno
.Exception e
) {
436 throw new com
.sun
.star
.uno
.RuntimeException(e
.getMessage());
439 if(mBridge
== null) {
440 Object connector
= null;
442 connector
= xLocalServiceManager
.createInstanceWithContext(
443 "com.sun.star.connection.Connector", xLocalContext
);
444 } catch (com
.sun
.star
.uno
.Exception e
) {
445 throw new com
.sun
.star
.uno
.RuntimeException(e
.getMessage());
447 XConnector connector_xConnector
= (XConnector
)UnoRuntime
.queryInterface(XConnector
.class, connector
);
448 // connect to the server
449 XConnection xConnection
= connector_xConnector
.connect(conDcp
);
450 // create the bridge name. This should not be necessary if we pass an
451 //empty string as bridge name into createBridge. Then we should always get
452 //a new bridge. This does not work because of (i51323). Therefore we
453 //create unique bridge names for the current process.
454 String sBridgeName
= "OOoBean_private_bridge_" + String
.valueOf(m_nBridgeCounter
++);
456 mBridge
= xBridgeFactory
.createBridge(sBridgeName
, protDcp
, xConnection
, null);
457 } catch (com
.sun
.star
.bridge
.BridgeExistsException e
) {
458 throw new com
.sun
.star
.uno
.RuntimeException(e
.getMessage());
461 rootObject
= mBridge
.getInstance(rootOid
);
468 * Retrives a path to the office program folder.
470 * @return The path to the office program folder.
472 static private String
getProgramPath()
474 if (mProgramPath
== null)
476 // determine name of executable soffice
477 String aExec
= OFFICE_APP_NAME
; // default for UNIX
478 String aOS
= System
.getProperty("os.name");
480 // running on Windows?
481 if (aOS
.startsWith("Windows"))
482 aExec
= OFFICE_APP_NAME
+ ".exe";
484 // add other non-UNIX operating systems here
487 // find soffice executable relative to this class's class loader:
488 File path
= NativeLibraryLoader
.getResource(
489 LocalOfficeConnection
.class.getClassLoader(), aExec
);
491 mProgramPath
= path
.getParent();
494 if ( mProgramPath
== null )
501 * Parses a connection URL.
502 * This method accepts a UNO URL with following format:<br />
504 * url := uno:localoffice[,<params>];urp;StarOffice.NamingService
505 * params := <path>[,<pipe>]
506 * path := path=<pathv>
507 * pipe := pipe=<pipev>
508 * pathv := platform_specific_path_to_the_local_office_distribution
509 * pipev := local_office_connection_pipe_name
514 * <li>"uno:localoffice,pipe=xyz_Office,path=/opt/openoffice11/program;urp;StarOffice.ServiceManager";
515 * <li>"uno:socket,host=localhost,port=8100;urp;StarOffice.ServiceManager";
518 * @param url This is UNO URL which describes the type of a connection.
519 * @exception java.net.MalformedURLException when inappropreate URL was
522 private void parseUnoUrlWithOfficePath(String url
, String prefix
)
523 throws java
.net
.MalformedURLException
525 // Extruct parameters.
526 int idx
= url
.indexOf(";urp;StarOffice.NamingService");
528 throw new java
.net
.MalformedURLException(
529 "Invalid UNO connection URL.");
530 String params
= url
.substring(prefix
.length(), idx
+ 1);
538 StringBuffer buffer
= new StringBuffer();
539 for(idx
= 0; idx
< params
.length(); idx
+= 1) {
540 ch
= params
.charAt(idx
);
542 case 0: // initial state
545 buffer
.delete(0, buffer
.length());
554 buffer
.delete(0, buffer
.length());
561 case 1: // parameter name
565 name
= buffer
.toString();
566 state
= (ch
== ' ')?
2: 3;
571 state
= -6; // error: invalid name
580 case 2: // equal between the name and the value
590 state
= -1; // error: missing '='
595 case 3: // value leading spaces
601 buffer
.delete(0, buffer
.length());
613 idx
-= 1; // put back the last read character
615 if (name
.equals("path")) {
617 path
= buffer
.toString();
619 state
= -3; // error: more then one 'path'
620 } else if (name
.equals("pipe")) {
622 pipe
= buffer
.toString();
624 state
= -4; // error: more then one 'pipe'
626 state
= -2; // error: unknown parameter
627 buffer
.delete(0, buffer
.length());
636 case 5: // a delimeter after the value
650 state
= -5; // error: ' ' inside the value
655 case 6: // leading spaces before next parameter name
661 buffer
.delete(0, buffer
.length());
669 throw new java
.net
.MalformedURLException(
670 "Invalid UNO connection URL.");
674 throw new java
.net
.MalformedURLException(
675 "Invalid UNO connection URL.");
677 // Set up the connection parameters.
684 /* replaces each substring aSearch in aString by aReplace.
686 StringBuffer.replaceAll() is not avaialable in Java 1.3.x.
688 private static String
replaceAll(String aString
, String aSearch
, String aReplace
)
690 StringBuffer aBuffer
= new StringBuffer(aString
);
692 int nPos
= aString
.length();
693 int nOfs
= aSearch
.length();
695 while ( ( nPos
= aString
.lastIndexOf( aSearch
, nPos
- 1 ) ) > -1 )
696 aBuffer
.replace( nPos
, nPos
+nOfs
, aReplace
);
698 return aBuffer
.toString();
702 /** creates a unique pipe name.
704 static String
getPipeName()
706 // turn user name into a URL and file system safe name (% chars will not work)
707 String aPipeName
= System
.getProperty("user.name") + OFFICE_ID_SUFFIX
;
708 aPipeName
= replaceAll( aPipeName
, "_", "%B7" );
709 return replaceAll( replaceAll( java
.net
.URLEncoder
.encode(aPipeName
), "+", "%20" ), "%", "_" );
713 * @para This is an implementation of the native office service.
715 private class OfficeService
716 implements NativeService
719 * Retrive the office service identifier.
721 * @return The identifier of the office service.
723 public String
getIdentifier()
726 return getPipeName();
732 * Starts the office process.
734 public void startupService()
735 throws java
.io
.IOException
737 int nSizeCmdArray
= 4;
738 String sOption
= null;
739 //examine if user specified command-line options in system properties.
740 //We may offer later a more sophisticated way of providing options if
741 //the need arises. Currently this is intended to ease the pain during
742 //development with pre-release builds of OOo where one wants to start
743 //OOo with the -norestore options. The value of the property is simple
744 //passed on to the Runtime.exec call.
746 sOption
= System
.getProperty("com.sun.star.officebean.Options");
749 } catch (java
.lang
.SecurityException e
)
753 // create call with arguments
754 String
[] cmdArray
= new String
[nSizeCmdArray
];
756 // read UNO_PATH environment variable to get path to soffice binary
757 String unoPath
= System
.getenv("UNO_PATH");
759 throw new java
.io
.IOException( "UNO_PATH environment variable is not set (required system path to the office program directory)" );
761 // cmdArray[0] = (new File(getProgramPath(), OFFICE_APP_NAME)).getPath();
762 cmdArray
[0] = (new File(unoPath
, OFFICE_APP_NAME
)).getPath();
763 cmdArray
[1] = "-nologo";
764 cmdArray
[2] = "-nodefault";
765 if ( mConnType
.equals( "pipe" ) )
766 cmdArray
[3] = "-accept=pipe,name=" + getIdentifier() + ";" +
767 mProtocol
+ ";" + mInitialObject
;
768 else if ( mConnType
.equals( "socket" ) )
769 cmdArray
[3] = "-accept=socket,port=" + mPort
+ ";urp";
771 throw new java
.io
.IOException( "not connection specified" );
774 cmdArray
[4] = sOption
;
777 mProcess
= Runtime
.getRuntime().exec(cmdArray
);
778 if ( mProcess
== null )
779 throw new RuntimeException( "cannot start soffice: " + cmdArray
);
780 new StreamProcessor(mProcess
.getInputStream(), System
.out
);
781 new StreamProcessor(mProcess
.getErrorStream(), System
.err
);
785 * Retrives the ammount of time to wait for the startup.
787 * @return The ammount of time to wait in seconds(?).
789 public int getStartupTime()
797 class StreamProcessor
extends Thread
799 java
.io
.InputStream m_in
;
800 java
.io
.PrintStream m_print
;
802 public StreamProcessor(final java
.io
.InputStream in
, final java
.io
.PrintStream out
)
810 java
.io
.BufferedReader r
= new java
.io
.BufferedReader(
811 new java
.io
.InputStreamReader(m_in
) );
814 String s
= r
.readLine();
820 } catch ( java
.io
.IOException e
) {
821 e
.printStackTrace( System
.err
);