1 /*
2  * This file is part of the LibreOffice project.
3  *
4  * This Source Code Form is subject to the terms of the Mozilla Public
5  * License, v. 2.0. If a copy of the MPL was not distributed with this
6  * file, You can obtain one at http://mozilla.org/MPL/2.0/.
7  *
8  * This file incorporates work covered by the following license notice:
9  *
10  *   Licensed to the Apache Software Foundation (ASF) under one or more
11  *   contributor license agreements. See the NOTICE file distributed
12  *   with this work for additional information regarding copyright
13  *   ownership. The ASF licenses this file to you under the Apache
14  *   License, Version 2.0 (the "License"); you may not use this file
15  *   except in compliance with the License. You may obtain a copy of
16  *   the License at http://www.apache.org/licenses/LICENSE-2.0 .
17  */
18 package helper;
19 
20 import com.sun.star.beans.XFastPropertySet;
21 import com.sun.star.bridge.XUnoUrlResolver;
22 import com.sun.star.container.XEnumeration;
23 import com.sun.star.container.XEnumerationAccess;
24 import com.sun.star.frame.XDesktop;
25 import com.sun.star.lang.XMultiComponentFactory;
26 import com.sun.star.lang.XMultiServiceFactory;
27 import com.sun.star.uno.UnoRuntime;
28 import com.sun.star.uno.XComponentContext;
29 import com.sun.star.util.XCloseable;
30 import com.sun.star.util.XStringSubstitution;
31 
32 import java.io.File;
33 import java.io.PrintWriter;
34 import java.io.UnsupportedEncodingException;
35 
36 import lib.TestParameters;
37 
38 import share.DescEntry;
39 import share.LogWriter;
40 
41 import util.DynamicClassLoader;
42 import util.PropertyName;
43 import util.utils;
44 
45 /**
46  * This class will connect the office and start it if possible
47  *
48  */
49 public class OfficeProvider implements AppProvider
50 {
51 
52     private boolean debug = false;
53 
54     /**
55      * copy the user layer to a safe place, usually to $TMP/user_backup$USER
56      */
backupUserLayer(TestParameters param, XMultiServiceFactory msf)57     private void backupUserLayer(TestParameters param, XMultiServiceFactory msf)
58     {
59         try
60         {
61             final XStringSubstitution sts = createStringSubstitution(msf);
62             debug = param.getBool(PropertyName.DEBUG_IS_ACTIVE);
63 
64             String userLayer = sts.getSubstituteVariableValue("$(user)");
65             userLayer = getDirSys(userLayer);
66             param.put("userLayer", userLayer);
67 
68             final String copyLayer = util.utils.getUsersTempDir() + System.getProperty("file.separator") +
69                     "user_backup" +
70                     System.getProperty("user.name");
71             param.put("copyLayer", copyLayer);
72 
73 
74             dbg(" copy '" + userLayer + "' ->" + copyLayer + "'");
75             // Slow machines the copy job could spend some time. To avoid activating of OfficeWatcher it must be pinged
76             OfficeWatcherPing owp = new OfficeWatcherPing((OfficeWatcher) param.get(PropertyName.OFFICE_WATCHER));
77             owp.start();
78 
79             deleteFilesAndDirector (new File(copyLayer));
80             FileTools.copyDirectory(new File(userLayer), new File(copyLayer), new String[]
81                     {
82                         "temp"
83                     });
84 
85             owp.finish();
86 
87         }
88         catch (com.sun.star.container.NoSuchElementException e)
89         {
90             System.out.println("User Variable '$(user)' not defined.");
91         }
92         catch (com.sun.star.uno.Exception e)
93         {
94             System.out.println("Couldn't backup user layer");
95             e.printStackTrace();
96         }
97         catch (java.io.IOException e)
98         {
99             System.out.println("Couldn't backup user layer");
100             e.printStackTrace();
101         }
102     }
103 
104     /**
105      * Dispose the office.
106      * This method can only be used, if the office was connected in the first
107      * place: getManager() was called first.
108      * @return return true if desktop is terminates, else false
109      */
disposeManager(lib.TestParameters param)110     public boolean disposeManager(lib.TestParameters param)
111     {
112 
113         XMultiServiceFactory msf = param.getMSF();
114 
115         if (msf == null)
116         {
117             return true;
118         }
119         else
120         {
121             XDesktop desk = null;
122 
123             try
124             {
125                 desk = UnoRuntime.queryInterface(XDesktop.class, msf.createInstance("com.sun.star.frame.Desktop"));
126             }
127             catch (com.sun.star.uno.Exception ue)
128             {
129                 return false;
130             }
131 
132             msf = null;
133 
134             if (desk != null)
135             {
136                 desk.terminate();
137 
138                 return true;
139             }
140             else
141             {
142                 return false;
143             }
144         }
145     }
146 
147     /**
148      * Method to get the ServiceManager of an Office
149      */
getManager(lib.TestParameters param)150     public Object getManager(lib.TestParameters param) throws UnsupportedEncodingException
151     {
152         String errorMessage = null;
153         boolean bAppExecutionHasWarning = false;
154         debug = param.getBool(PropertyName.DEBUG_IS_ACTIVE);
155 
156         String additionalArgs = (String) param.get(
157                 "AdditionalConnectionArguments");
158 
159         if (additionalArgs == null)
160         {
161             additionalArgs = ";";
162         }
163         else
164         {
165             additionalArgs = "," + additionalArgs + ";";
166         }
167 
168         final String cncstr = "uno:" + param.get("ConnectionString") + ";urp" +
169                 additionalArgs + "StarOffice.ServiceManager";
170 
171         System.out.println("Connecting the Office with " + cncstr);
172 
173         XMultiServiceFactory msf = connectOffice(cncstr);
174 
175         // if the office is running and the office crashes while testing it could
176         // be useful to restart the office if possible and continuing the tests.
177         // Example: the UNO-API-Tests in the projects will be executed by calling
178         // 'damke'. This connects to an existing office. If the office crashes
179         // it is useful to restart the office and continuing the tests.
180         if ((param.getBool(util.PropertyName.AUTO_RESTART)) && (msf != null))
181         {
182             makeAppExecCommand(msf, param);
183         }
184 
185         if (msf == null)
186         {
187             String exc = "";
188             Exception exConnectFailed = null;
189             boolean isExecutable = false;
190             boolean isAppKnown = ((cncstr.indexOf("host=localhost") > 0) || (cncstr.indexOf("pipe,name=") > 0));
191             isAppKnown &= ((String) param.get("AppExecutionCommand")).length() != 0;
192 
193             if (isAppKnown)
194             {
195                 dbg("Local Connection trying to start the Office");
196 
197                 //ensure that a pending officewatcher gets finished before a new
198                 //office is started
199                 final OfficeWatcher ow_old = (OfficeWatcher) param.get("Watcher");
200 
201                 if (ow_old != null)
202                 {
203                     ow_old.finish = true;
204                 }
205 
206                 final String cmd = (String) param.get("AppExecutionCommand");
207                 dbg("AppExecutionCommand: " + cmd);
208                 // validate the AppExecutionCommand, but try it out anyway.
209                 // keep the error message for later.
210                 errorMessage =
211                         util.utils.validateAppExecutionCommand(cmd, (String) param.get("OperatingSystem"));
212                 if (errorMessage.startsWith("Error"))
213                 {
214                     System.out.println(errorMessage);
215                     return null;
216                 }
217                 bAppExecutionHasWarning = !errorMessage.equals("OK");
218 
219                 final DynamicClassLoader dcl = new DynamicClassLoader();
220                 final LogWriter log = (LogWriter) dcl.getInstance(
221                         (String) param.get("LogWriter"));
222 
223                 //create empty entry
224                 final DescEntry Entry = new DescEntry();
225                 Entry.entryName = "office";
226                 Entry.longName = "office";
227                 Entry.EntryType = "placebo";
228                 Entry.isOptional = false;
229                 Entry.isToTest = false;
230                 Entry.SubEntryCount = 0;
231                 Entry.hasErrorMsg = false;
232                 Entry.State = "non possible";
233                 Entry.UserDefinedParams = param;
234 
235                 log.initialize(Entry, debug);
236 
237                 final ProcessHandler ph = new ProcessHandler(cmd, (PrintWriter) log);
238                 isExecutable = ph.executeAsynchronously();
239 
240                 if (isExecutable)
241                 {
242                     param.put("AppProvider", ph);
243                     final OfficeWatcher ow = new OfficeWatcher(param);
244                     param.put("Watcher", ow);
245                     ow.start();
246                     ow.ping();
247                 }
248 
249                 int k = 0;
250 
251                 // wait up to 21 seconds to get an office connection
252                 while ((k < 42) && (msf == null))
253                 {
254                     try
255                     {
256                         msf = connect(cncstr);
257                     }
258                     catch (com.sun.star.uno.Exception ue)
259                     {
260                         exConnectFailed = ue;
261                         exc = ue.getMessage();
262                     }
263                     catch (java.lang.Exception je)
264                     {
265                         exConnectFailed = je;
266                         exc = je.getMessage();
267                     }
268                     if (msf == null)
269                     {
270                         util.utils.pause(500 * k);
271                     }
272                     k++;
273                 }
274 
275                 if (msf == null)
276                 {
277                     System.out.println("Exception while connecting.\n" + exConnectFailed);
278                     if (exc != null)
279                     {
280                         System.out.println(exc);
281                     }
282                     if (bAppExecutionHasWarning)
283                     {
284                         System.out.println(errorMessage);
285                     }
286                 }
287                 else if (isExecutable)
288                 {
289                     backupUserLayer(param, msf);
290                 }
291             }
292             else
293             {
294                 System.out.println("Could not connect an Office and cannot start one.\n".concat("please start an office with following parameter:\n").
295                         concat("\nsoffice --accept=").concat((String) param.get("ConnectionString")).concat(";urp;\n"));
296             }
297         }
298 
299         return msf;
300     }
301 
302     /**
303      * Connect an Office
304      * @param connectStr
305      * @return
306      * @throws com.sun.star.uno.Exception
307      * @throws com.sun.star.uno.RuntimeException
308      * @throws com.sun.star.connection.NoConnectException
309      * @throws Exception
310      */
connect(String connectStr)311     private XMultiServiceFactory connect(String connectStr)
312             throws com.sun.star.uno.Exception,
313             com.sun.star.uno.RuntimeException,
314             com.sun.star.connection.NoConnectException,
315             Exception
316     {
317 
318         // Get component context
319         final XComponentContext xcomponentcontext = com.sun.star.comp.helper.Bootstrap.createInitialComponentContext(null);
320 
321         // initial serviceManager
322         final XMultiComponentFactory xLocalServiceManager = xcomponentcontext.getServiceManager();
323 
324         // create a connector, so that it can contact the office
325         final Object xUrlResolver = xLocalServiceManager.createInstanceWithContext("com.sun.star.bridge.UnoUrlResolver", xcomponentcontext);
326         final XUnoUrlResolver urlResolver = UnoRuntime.queryInterface(XUnoUrlResolver.class, xUrlResolver);
327 
328         final Object rInitialObject = urlResolver.resolve(connectStr);
329 
330         XMultiServiceFactory xMSF = null;
331 
332         if (rInitialObject != null)
333         {
334             // debug = true;
335             dbg("resolved url");
336 
337             xMSF = UnoRuntime.queryInterface(XMultiServiceFactory.class, rInitialObject);
338         }
339 
340         return xMSF;
341     }
342 
343     /**
344      * Close an office.
345      * @param param The test parameters.
346      * @param closeIfPossible If true, close even if
347      * it was running before the test
348      */
closeExistingOffice(lib.TestParameters param, boolean closeIfPossible)349     public boolean closeExistingOffice(lib.TestParameters param, boolean closeIfPossible)
350     {
351 
352         XMultiServiceFactory msf = param.getMSF();
353         final boolean alreadyConnected = (msf != null);
354         debug = param.getBool(PropertyName.DEBUG_IS_ACTIVE);
355 
356         if (alreadyConnected)
357         {
358             dbg("try to get ProcessHandler");
359 
360             final ProcessHandler ph = (ProcessHandler) param.get("AppProvider");
361 
362             if (ph != null)
363             {
364                 dbg("ProcessHandler != null");
365 
366                 disposeOffice(msf, param);
367 
368                 // dispose watcher in case it's still running.
369                 dbg("try to get OfficeWatcher");
370 
371                 final OfficeWatcher ow = (OfficeWatcher) param.get("Watcher");
372 
373                 if ((ow != null) && ow.isAlive())
374                 {
375                     dbg("OfficeWatcher will be finished");
376                     ow.finish = true;
377                 }
378                 else
379                 {
380                     dbg("OfficeWatcher seems to be finished");
381                 }
382 
383                 return true;
384             }
385             else
386             {
387                 if (closeIfPossible)
388                 {
389                     return disposeOffice(msf, param);
390                 }
391             }
392         }
393         else
394         {
395             final String cncstr = "uno:" + param.get("ConnectionString") +
396                     ";urp;StarOffice.ServiceManager";
397             dbg("try to connect office");
398             msf = connectOffice(cncstr);
399 
400             if (closeIfPossible)
401             {
402                 return disposeOffice(msf, param);
403             }
404         }
405         dbg("closeExistingOffice finished");
406         return true;
407     }
408 
connectOffice(String cncstr)409     private XMultiServiceFactory connectOffice(String cncstr)
410     {
411         XMultiServiceFactory msf = null;
412         String exc = "";
413         // debug = true;
414 
415         dbg("trying to connect to " + cncstr);
416 
417         try
418         {
419             msf = connect(cncstr);
420         }
421         catch (com.sun.star.uno.Exception ue)
422         {
423             exc = ue.getMessage();
424         }
425         catch (java.lang.Exception je)
426         {
427             exc = je.getMessage();
428         }
429 
430         if (debug && exc != null && exc.length() != 0)
431         {
432             dbg("Could not connect an Office. " + exc);
433         }
434 
435         return msf;
436     }
437 
disposeOffice(XMultiServiceFactory msf, TestParameters param)438     private boolean disposeOffice(XMultiServiceFactory msf,
439             TestParameters param)
440     {
441         XDesktop desk = null;
442 
443         debug = param.getBool(PropertyName.DEBUG_IS_ACTIVE);
444 
445         boolean result = true;
446 
447         if (msf != null)
448         {
449 
450             // disable QuickStarter
451             try
452             {
453                 Object quickStarter = msf.createInstance("com.sun.star.office.Quickstart");
454                 XFastPropertySet fps = UnoRuntime.queryInterface(XFastPropertySet.class, quickStarter);
455                 fps.setFastPropertyValue(0, false);
456             }
457             catch (com.sun.star.uno.Exception ex)
458             {
459                 dbg("ERROR: Could not disable QuickStarter: " + ex.toString());
460             }
461 
462             try
463             {
464                 desk = UnoRuntime.queryInterface(XDesktop.class, msf.createInstance("com.sun.star.frame.Desktop"));
465                 msf = null;
466 
467                 if (desk != null)
468                 {
469                     final boolean allClosed = closeAllWindows(desk);
470 
471                     if (!allClosed)
472                     {
473                         dbg("Couldn't close all office windows!");
474                     }
475 
476                     dbg("Trying to terminate the desktop");
477 
478                     desk.terminate();
479                     dbg("Desktop terminated");
480                 }
481             }
482             catch (com.sun.star.uno.Exception ue)
483             {
484                 result = false;
485             }
486             catch (com.sun.star.lang.DisposedException ue)
487             {
488                 result = false;
489             }
490         }
491 
492         final ProcessHandler ph = (ProcessHandler) param.get("AppProvider");
493 
494         if (ph != null)
495         {
496             // dispose watcher in case it's still running.
497             final OfficeWatcher ow = (OfficeWatcher) param.get("Watcher");
498 
499             if ((ow != null) && ow.isAlive())
500             {
501                 ow.finish = true;
502             }
503 
504             ph.kill();
505         }
506 
507         param.remove("AppProvider");
508         param.remove("ServiceFactory");
509 
510         //copy user_backup into user layer
511         try
512         {
513             final String userLayer = (String) param.get("userLayer");
514             final String copyLayer = (String) param.get("copyLayer");
515             if (userLayer != null && copyLayer != null)
516             {
517                 deleteFilesAndDirector(new File(userLayer));
518                 final File copyFile = new File(copyLayer);
519                 dbg("copy '" + copyFile + "' -> '" + userLayer + "'");
520                 FileTools.copyDirectory(copyFile, new File(userLayer), new String[]
521                         {
522                             "temp"
523                         });
524                 dbg("copy '" + copyFile + "' -> '" + userLayer + "' finished");
525 
526             // remove all user_backup folder in temp dir
527             // this is for the case the runner was killed and some old backup folder still stay in temp dir
528 
529 
530             }
531             else
532             {
533                 System.out.println("Cannot copy layer: '" + copyLayer + "' back to user layer: '" + userLayer + "'");
534             }
535         }
536         catch (java.io.IOException e)
537         {
538             dbg("Couldn't recover from backup\n" + e.getMessage());
539         }
540         return result;
541     }
542 
closeAllWindows(XDesktop desk)543     private boolean closeAllWindows(XDesktop desk)
544     {
545         final XEnumerationAccess compEnumAccess = desk.getComponents();
546         final XEnumeration compEnum = compEnumAccess.createEnumeration();
547         boolean res = true;
548 
549         try
550         {
551             while (compEnum.hasMoreElements())
552             {
553                 final XCloseable closer = UnoRuntime.queryInterface(XCloseable.class, compEnum.nextElement());
554 
555                 if (closer != null)
556                 {
557                     closer.close(true);
558                 }
559             }
560         }
561         catch (com.sun.star.util.CloseVetoException cve)
562         {
563             res = false;
564         }
565         catch (com.sun.star.container.NoSuchElementException nsee)
566         {
567             res = false;
568         }
569         catch (com.sun.star.lang.WrappedTargetException wte)
570         {
571             res = false;
572         }
573 
574         return res;
575     }
576 
createStringSubstitution(XMultiServiceFactory xMSF)577     private static XStringSubstitution createStringSubstitution(XMultiServiceFactory xMSF) throws com.sun.star.uno.Exception
578     {
579         Object xPathSubst = xMSF.createInstance(
580                     "com.sun.star.util.PathSubstitution");
581         return UnoRuntime.queryInterface(XStringSubstitution.class, xPathSubst);
582     }
583 
584     /**
585      * converts directory without 'file:///' prefix.
586      * and System dependent file separator
587      */
getDirSys(String dir)588     private static String getDirSys(String dir)
589     {
590         String sysDir = "";
591 
592         final int idx = dir.indexOf("file://");
593 
594         final int idx2 = dir.indexOf("file:///");
595 
596         // remove leading 'file://'
597         if (idx < 0)
598         {
599             sysDir = dir;
600         }
601         else
602         {
603             sysDir = dir.substring("file://".length());
604         }
605 
606         sysDir = sysDir.replace("%20", " ");
607 
608         // append '/' if not there (e.g. linux)
609         if (sysDir.charAt(sysDir.length() - 1) != '/')
610         {
611             sysDir += "/";
612         }
613 
614         // remove leading '/' and replace others with '\' on windows machines
615         final String sep = System.getProperty("file.separator");
616 
617         if (sep.equalsIgnoreCase("\\"))
618         {
619             if (idx2 >= 0)
620             {
621                 sysDir = sysDir.substring(1);
622             }
623             else
624             {
625                 //network path
626                 sysDir = "//" + sysDir;
627             }
628             sysDir = sysDir.replace('/', '\\');
629         }
630 
631         return sysDir;
632     }
633 
634     /**
635      * If the office is connected but the <CODE>AppExecutionCommand</CODE> is not set,
636      * this function asks the office for its location and fill the
637      * <CODE>AppExecutionCommand</CODE> with valid content.
638      * This function was only called if parameter <CODE>AutoRestart</CODE> is set.
639      * @param msf the <CODE>MultiServiceFactory</CODE>
640      * @param param the <CODE>TestParameters</CODE>
641      */
makeAppExecCommand(XMultiServiceFactory msf, TestParameters param)642     private void makeAppExecCommand(XMultiServiceFactory msf, TestParameters param)
643     {
644         debug = param.getBool(PropertyName.DEBUG_IS_ACTIVE);
645 
646         // get existing AppExecutionCommand if available, else empty string
647         String command = (String) param.get(util.PropertyName.APP_EXECUTION_COMMAND);
648 
649         String connectionString;
650         if (param.getBool(util.PropertyName.USE_PIPE_CONNECTION))
651         {
652             // This is the default behaviour
653             connectionString = (String) param.get(util.PropertyName.PIPE_CONNECTION_STRING);
654         }
655         else
656         {
657             // is used if UsePipeConnection=false
658             connectionString = (String) param.get(util.PropertyName.CONNECTION_STRING);
659         }
660 
661         String sysBinDir = "";
662 
663         try
664         {
665             sysBinDir = utils.getSystemURL(utils.expandMacro(msf, "$SYSBINDIR"));
666         }
667         catch (java.lang.Exception e)
668         {
669             dbg("could not get system binary directory");
670             return;
671         }
672 
673         // does the existing command show to the connected office?
674         if (command.indexOf(sysBinDir) == -1)
675         {
676             command = sysBinDir + System.getProperty("file.separator") + "soffice" +
677                     " --norestore --accept=" + connectionString + ";urp;";
678         }
679 
680         dbg("update AppExecutionCommand: " + command);
681 
682         param.put(util.PropertyName.APP_EXECUTION_COMMAND, command);
683     }
684 
dbg(String message)685     private void dbg(String message)
686     {
687         if (debug)
688         {
689             System.out.println(utils.getDateTime() + "OfficeProvider: " + message);
690         }
691 
692     }
693 
694     private static class OfficeWatcherPing extends Thread
695     {
696 
697         private final OfficeWatcher ow;
698         private boolean bStop = false;
699 
OfficeWatcherPing(OfficeWatcher ow)700         public OfficeWatcherPing(OfficeWatcher ow)
701         {
702             this.ow = ow;
703         }
704 
705         @Override
run()706         public void run()
707         {
708             System.out.println(utils.getDateTime() + "OfficeProvider:Owp: start ");
709 
710             while (!bStop)
711             {
712                 System.out.println(utils.getDateTime() + "OfficeProvider:Owp: ping ");
713                 ow.ping();
714                 System.out.println(utils.getDateTime() + "OfficeProvider:Owp: sleep ");
715                 util.utils.pause(1000);
716             }
717 
718         }
719 
finish()720         public void finish()
721         {
722             synchronized(this)
723             {
724                 bStop = true;
725                 System.out.println(utils.getDateTime() + "OfficeProvider:Owp: stop ");
726 
727                 notify();
728             }
729         }
730     }
731 
deleteFilesAndDirector(File file)732     private void deleteFilesAndDirector(File file)
733     {
734         File f = file;
735         if(f.isDirectory())
736         {
737             File files[] = f.listFiles();
738             int filesLength = files != null ? files.length : 0;
739             for(int i = 0; i < filesLength; ++i)
740             {
741                 deleteFilesAndDirector(files[i]);
742             }
743             boolean bDeleteOk = f.delete();
744             if (!bDeleteOk) {
745                 System.out.println("delete failed");
746             }
747         }
748         else if (f.isFile())
749         {
750             boolean bDeleteOk = f.delete();
751             if (!bDeleteOk) {
752                 System.out.println("delete failed");
753             }
754         }
755     }
756 }
757