Update ooo320-m1
[ooovba.git] / bean / com / sun / star / comp / beans / LocalOfficeConnection.java
blobbaf6e07b21916fb610489c08a047309e9230f33b
1 /*************************************************************************
3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4 *
5 * Copyright 2008 by Sun Microsystems, Inc.
7 * OpenOffice.org - a multi-platform office productivity suite
9 * $RCSfile: LocalOfficeConnection.java,v $
10 * $Revision: 1.12 $
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;
34 import java.io.File;
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;
53 /**
54 * This class reprecents a connection to the local office application.
56 * @since OOo 2.0.0
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;
72 private String mURL;
73 private String mConnType;
74 private String mPipe;
75 private String mPort;
76 private String mProtocol;
77 private String mInitialObject;
79 private List mComponents = new Vector();
81 private static long m_nBridgeCounter = 0;
82 //-------------------------------------------------------------------------
83 static
85 // preload shared libraries whichs import lips are linked to officebean
86 if ( System.getProperty( "os.name" ).startsWith( "Windows" ) )
88 try
90 NativeLibraryLoader.loadLibrary(LocalOfficeConnection.class.getClassLoader(), "msvcr70");
92 catch (Throwable e)
94 // loading twice would fail
95 System.err.println( "cannot find msvcr70" );
98 try
100 NativeLibraryLoader.loadLibrary(LocalOfficeConnection.class.getClassLoader(), "msvcr71");
102 catch (Throwable e)
104 // loading twice would fail
105 System.err.println( "cannot find msvcr71" );
108 try
110 NativeLibraryLoader.loadLibrary(LocalOfficeConnection.class.getClassLoader(), "uwinapi");
112 catch (Throwable e)
114 // loading twice would fail
115 System.err.println( "cannot find uwinapi" );
118 try
120 NativeLibraryLoader.loadLibrary(LocalOfficeConnection.class.getClassLoader(), "jawt");
122 catch (Throwable e)
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 //-------------------------------------------------------------------------
134 // debugging method
135 private void dbgPrint( String aMessage )
137 System.err.println( aMessage );
141 * Constructor.
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()
150 // init member vars
151 try
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 />
162 * <pre>
163 * url := uno:localoffice[,&lt;params&gt;];urp;StarOffice.ServiceManager
164 * params := &lt;path&gt;[,&lt;pipe&gt;]
165 * path := path=&lt;pathv&gt;
166 * pipe := pipe=&lt;pipev&gt;
167 * pathv := platform_specific_path_to_the_local_office_distribution
168 * pipev := local_office_connection_pipe_name
169 * </pre>
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
176 mURL = null;
178 String prefix = "uno:localoffice";
179 if ( url.startsWith(prefix) )
180 parseUnoUrlWithOfficePath( url, prefix );
181 else
185 UnoUrl aURL = UnoUrl.parseUnoUrl( url );
186 mProgramPath = null;
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.");
199 mURL = url;
203 * Sets an AWT container catory.
205 * @param containerFactory This is a application provided AWT container
206 * factory.
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();
226 return mContext;
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 ) {}
253 mComponents.clear();
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.
261 if (mBridge != null)
263 XComponent comp = (XComponent)UnoRuntime.queryInterface(
264 XComponent.class, mBridge);
265 if (comp != null)
266 comp.dispose();
267 else
268 System.err.println("LocalOfficeConnection: could not dispose bridge!");
270 mBridge = null;
273 mContainerFactory = null;
274 mContext = 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()
302 try
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;
313 try
315 aInitialObject = resolve(xLocalContext, mURL);
317 catch( com.sun.star.connection.NoConnectException e )
319 // launch soffice
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 )
327 try
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 )
337 throw aEx;
342 finally
346 // XComponentContext
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() );
376 e.printStackTrace();
377 System.out.println( "--- end." );
378 throw e;
380 catch( java.lang.Exception e )
382 System.out.println( "java.lang.Exception: " );
383 System.out.println( e );
384 e.printStackTrace();
385 System.out.println( "--- end." );
386 throw new com.sun.star.uno.RuntimeException( e.toString() );
389 return null;
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
406 conDcp = dcp;
407 protDcp = "iiop";
408 rootOid = "classic_uno";
410 else { // new style
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();
430 try {
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());
438 synchronized(this) {
439 if(mBridge == null) {
440 Object connector= null;
441 try {
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++);
455 try {
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);
462 return rootObject;
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
485 // ...
487 // find soffice executable relative to this class's class loader:
488 File path = NativeLibraryLoader.getResource(
489 LocalOfficeConnection.class.getClassLoader(), aExec);
490 if (path != null)
491 mProgramPath = path.getParent();
493 // default is ""
494 if ( mProgramPath == null )
495 mProgramPath = "";
497 return mProgramPath;
501 * Parses a connection URL.
502 * This method accepts a UNO URL with following format:<br />
503 * <pre>
504 * url := uno:localoffice[,&lt;params&gt;];urp;StarOffice.NamingService
505 * params := &lt;path&gt;[,&lt;pipe&gt;]
506 * path := path=&lt;pathv&gt;
507 * pipe := pipe=&lt;pipev&gt;
508 * pathv := platform_specific_path_to_the_local_office_distribution
509 * pipev := local_office_connection_pipe_name
510 * </pre>
512 * <h4>Examples</h4>
513 * <ul>
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";
516 * </ul>
518 * @param url This is UNO URL which describes the type of a connection.
519 * @exception java.net.MalformedURLException when inappropreate URL was
520 * provided.
522 private void parseUnoUrlWithOfficePath(String url, String prefix)
523 throws java.net.MalformedURLException
525 // Extruct parameters.
526 int idx = url.indexOf(";urp;StarOffice.NamingService");
527 if (idx < 0)
528 throw new java.net.MalformedURLException(
529 "Invalid UNO connection URL.");
530 String params = url.substring(prefix.length(), idx + 1);
532 // Parse parameters.
533 String name = null;
534 String path = null;
535 String pipe = null;
536 char ch;
537 int state = 0;
538 StringBuffer buffer = new StringBuffer();
539 for(idx = 0; idx < params.length(); idx += 1) {
540 ch = params.charAt(idx);
541 switch (state) {
542 case 0: // initial state
543 switch(ch) {
544 case ',':
545 buffer.delete(0, buffer.length());
546 state = 1;
547 break;
549 case ';':
550 state = 7;
551 break;
553 default:
554 buffer.delete(0, buffer.length());
555 buffer.append(ch);
556 state = 1;
557 break;
559 break;
561 case 1: // parameter name
562 switch(ch) {
563 case ' ':
564 case '=':
565 name = buffer.toString();
566 state = (ch == ' ')? 2: 3;
567 break;
569 case ',':
570 case ';':
571 state = -6; // error: invalid name
572 break;
574 default:
575 buffer.append(ch);
576 break;
578 break;
580 case 2: // equal between the name and the value
581 switch(ch) {
582 case '=':
583 state = 3;
584 break;
586 case ' ':
587 break;
589 default:
590 state = -1; // error: missing '='
591 break;
593 break;
595 case 3: // value leading spaces
596 switch(ch) {
597 case ' ':
598 break;
600 default:
601 buffer.delete(0, buffer.length());
602 buffer.append(ch);
603 state = 4;
604 break;
606 break;
608 case 4: // value
609 switch(ch) {
610 case ' ':
611 case ',':
612 case ';':
613 idx -= 1; // put back the last read character
614 state = 5;
615 if (name.equals("path")) {
616 if (path == null)
617 path = buffer.toString();
618 else
619 state = -3; // error: more then one 'path'
620 } else if (name.equals("pipe")) {
621 if (pipe == null)
622 pipe = buffer.toString();
623 else
624 state = -4; // error: more then one 'pipe'
625 } else
626 state = -2; // error: unknown parameter
627 buffer.delete(0, buffer.length());
628 break;
630 default:
631 buffer.append(ch);
632 break;
634 break;
636 case 5: // a delimeter after the value
637 switch(ch) {
638 case ' ':
639 break;
641 case ',':
642 state = 6;
643 break;
645 case ';':
646 state = 7;
647 break;
649 default:
650 state = -5; // error: ' ' inside the value
651 break;
653 break;
655 case 6: // leading spaces before next parameter name
656 switch(ch) {
657 case ' ':
658 break;
660 default:
661 buffer.delete(0, buffer.length());
662 buffer.append(ch);
663 state = 1;
664 break;
666 break;
668 default:
669 throw new java.net.MalformedURLException(
670 "Invalid UNO connection URL.");
673 if (state != 7)
674 throw new java.net.MalformedURLException(
675 "Invalid UNO connection URL.");
677 // Set up the connection parameters.
678 if (path != null)
679 mProgramPath = path;
680 if (pipe != null)
681 mPipe = pipe;
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" ), "%", "_" );
712 /**
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()
725 if ( mPipe == null)
726 return getPipeName();
727 else
728 return mPipe;
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.
745 try {
746 sOption = System.getProperty("com.sun.star.officebean.Options");
747 if (sOption != null)
748 nSizeCmdArray ++;
749 } catch (java.lang.SecurityException e)
751 e.printStackTrace();
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");
758 if (unoPath == null)
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";
770 else
771 throw new java.io.IOException( "not connection specified" );
773 if (sOption != null)
774 cmdArray[4] = sOption;
776 // start process
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()
791 return 60;
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)
804 m_in = in;
805 m_print = out;
806 start();
809 public void run() {
810 java.io.BufferedReader r = new java.io.BufferedReader(
811 new java.io.InputStreamReader(m_in) );
812 try {
813 for ( ; ; ) {
814 String s = r.readLine();
815 if ( s == null ) {
816 break;
818 m_print.println(s);
820 } catch ( java.io.IOException e ) {
821 e.printStackTrace( System.err );