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 continue the tests.
177         // Example: the UNO-API-Tests in the projects will be executed by calling
178         // 'dmake'.
179         if ((param.getBool(util.PropertyName.AUTO_RESTART)) && (msf != null))
180         {
181             makeAppExecCommand(msf, param);
182         }
183 
184         if (msf == null)
185         {
186             String exc = "";
187             Exception exConnectFailed = null;
188             boolean isExecutable = false;
189             boolean isAppKnown = ((cncstr.indexOf("host=localhost") > 0) || (cncstr.indexOf("pipe,name=") > 0));
190             isAppKnown &= ((String) param.get("AppExecutionCommand")).length() != 0;
191 
192             if (isAppKnown)
193             {
194                 dbg("Local Connection trying to start the Office");
195 
196                 //ensure that a pending officewatcher gets finished before a new
197                 //office is started
198                 final OfficeWatcher ow_old = (OfficeWatcher) param.get("Watcher");
199 
200                 if (ow_old != null)
201                 {
202                     ow_old.finish = true;
203                 }
204 
205                 final String cmd = (String) param.get("AppExecutionCommand");
206                 dbg("AppExecutionCommand: " + cmd);
207                 // validate the AppExecutionCommand, but try it out anyway.
208                 // keep the error message for later.
209                 errorMessage =
210                         util.utils.validateAppExecutionCommand(cmd, (String) param.get("OperatingSystem"));
211                 if (errorMessage.startsWith("Error"))
212                 {
213                     System.out.println(errorMessage);
214                     return null;
215                 }
216                 bAppExecutionHasWarning = !errorMessage.equals("OK");
217 
218                 final DynamicClassLoader dcl = new DynamicClassLoader();
219                 final LogWriter log = (LogWriter) dcl.getInstance(
220                         (String) param.get("LogWriter"));
221 
222                 //create empty entry
223                 final DescEntry Entry = new DescEntry();
224                 Entry.entryName = "office";
225                 Entry.longName = "office";
226                 Entry.EntryType = "placebo";
227                 Entry.isOptional = false;
228                 Entry.isToTest = false;
229                 Entry.SubEntryCount = 0;
230                 Entry.hasErrorMsg = false;
231                 Entry.State = "non possible";
232                 Entry.UserDefinedParams = param;
233 
234                 log.initialize(Entry, debug);
235 
236                 final ProcessHandler ph = new ProcessHandler(cmd, (PrintWriter) log);
237                 isExecutable = ph.executeAsynchronously();
238 
239                 if (isExecutable)
240                 {
241                     param.put("AppProvider", ph);
242                     final OfficeWatcher ow = new OfficeWatcher(param);
243                     param.put("Watcher", ow);
244                     ow.start();
245                     ow.ping();
246                 }
247 
248                 int k = 0;
249 
250                 // wait up to 21 seconds to get an office connection
251                 while ((k < 42) && (msf == null))
252                 {
253                     try
254                     {
255                         msf = connect(cncstr);
256                     }
257                     catch (com.sun.star.uno.Exception ue)
258                     {
259                         exConnectFailed = ue;
260                         exc = ue.getMessage();
261                     }
262                     catch (java.lang.Exception je)
263                     {
264                         exConnectFailed = je;
265                         exc = je.getMessage();
266                     }
267                     if (msf == null)
268                     {
269                         util.utils.pause(500 * k);
270                     }
271                     k++;
272                 }
273 
274                 if (msf == null)
275                 {
276                     System.out.println("Exception while connecting.\n" + exConnectFailed);
277                     if (exc != null)
278                     {
279                         System.out.println(exc);
280                     }
281                     if (bAppExecutionHasWarning)
282                     {
283                         System.out.println(errorMessage);
284                     }
285                 }
286                 else if (isExecutable)
287                 {
288                     backupUserLayer(param, msf);
289                 }
290             }
291             else
292             {
293                 System.out.println("Could not connect an Office and cannot start one.\n".concat("please start an office with following parameter:\n").
294                         concat("\nsoffice --accept=").concat((String) param.get("ConnectionString")).concat(";urp;\n"));
295             }
296         }
297 
298         return msf;
299     }
300 
301     /**
302      * Connect an Office
303      * @param connectStr
304      * @return
305      * @throws com.sun.star.uno.Exception
306      * @throws com.sun.star.uno.RuntimeException
307      * @throws com.sun.star.connection.NoConnectException
308      * @throws Exception
309      */
connect(String connectStr)310     private XMultiServiceFactory connect(String connectStr)
311             throws com.sun.star.uno.Exception,
312             com.sun.star.uno.RuntimeException,
313             com.sun.star.connection.NoConnectException,
314             Exception
315     {
316 
317         // Get component context
318         final XComponentContext xcomponentcontext = com.sun.star.comp.helper.Bootstrap.createInitialComponentContext(null);
319 
320         // initial serviceManager
321         final XMultiComponentFactory xLocalServiceManager = xcomponentcontext.getServiceManager();
322 
323         // create a connector, so that it can contact the office
324         final Object xUrlResolver = xLocalServiceManager.createInstanceWithContext("com.sun.star.bridge.UnoUrlResolver", xcomponentcontext);
325         final XUnoUrlResolver urlResolver = UnoRuntime.queryInterface(XUnoUrlResolver.class, xUrlResolver);
326 
327         final Object rInitialObject = urlResolver.resolve(connectStr);
328 
329         XMultiServiceFactory xMSF = null;
330 
331         if (rInitialObject != null)
332         {
333             // debug = true;
334             dbg("resolved url");
335 
336             xMSF = UnoRuntime.queryInterface(XMultiServiceFactory.class, rInitialObject);
337         }
338 
339         return xMSF;
340     }
341 
342     /**
343      * Close an office.
344      * @param param The test parameters.
345      * @param closeIfPossible If true, close even if
346      * it was running before the test
347      */
closeExistingOffice(lib.TestParameters param, boolean closeIfPossible)348     public boolean closeExistingOffice(lib.TestParameters param, boolean closeIfPossible)
349     {
350 
351         XMultiServiceFactory msf = param.getMSF();
352         final boolean alreadyConnected = (msf != null);
353         debug = param.getBool(PropertyName.DEBUG_IS_ACTIVE);
354 
355         if (alreadyConnected)
356         {
357             dbg("try to get ProcessHandler");
358 
359             final ProcessHandler ph = (ProcessHandler) param.get("AppProvider");
360 
361             if (ph != null)
362             {
363                 dbg("ProcessHandler != null");
364 
365                 disposeOffice(msf, param);
366 
367                 // dispose watcher in case it's still running.
368                 dbg("try to get OfficeWatcher");
369 
370                 final OfficeWatcher ow = (OfficeWatcher) param.get("Watcher");
371 
372                 if ((ow != null) && ow.isAlive())
373                 {
374                     dbg("OfficeWatcher will be finished");
375                     ow.finish = true;
376                 }
377                 else
378                 {
379                     dbg("OfficeWatcher seems to be finished");
380                 }
381 
382                 return true;
383             }
384             else
385             {
386                 if (closeIfPossible)
387                 {
388                     return disposeOffice(msf, param);
389                 }
390             }
391         }
392         else
393         {
394             final String cncstr = "uno:" + param.get("ConnectionString") +
395                     ";urp;StarOffice.ServiceManager";
396             dbg("try to connect office");
397             msf = connectOffice(cncstr);
398 
399             if (closeIfPossible)
400             {
401                 return disposeOffice(msf, param);
402             }
403         }
404         dbg("closeExistingOffice finished");
405         return true;
406     }
407 
connectOffice(String cncstr)408     private XMultiServiceFactory connectOffice(String cncstr)
409     {
410         XMultiServiceFactory msf = null;
411         String exc = "";
412         // debug = true;
413 
414         dbg("trying to connect to " + cncstr);
415 
416         try
417         {
418             msf = connect(cncstr);
419         }
420         catch (com.sun.star.uno.Exception ue)
421         {
422             exc = ue.getMessage();
423         }
424         catch (java.lang.Exception je)
425         {
426             exc = je.getMessage();
427         }
428 
429         if (debug && exc != null && exc.length() != 0)
430         {
431             dbg("Could not connect an Office. " + exc);
432         }
433 
434         return msf;
435     }
436 
disposeOffice(XMultiServiceFactory msf, TestParameters param)437     private boolean disposeOffice(XMultiServiceFactory msf,
438             TestParameters param)
439     {
440         XDesktop desk = null;
441 
442         debug = param.getBool(PropertyName.DEBUG_IS_ACTIVE);
443 
444         boolean result = true;
445 
446         if (msf != null)
447         {
448 
449             // disable QuickStarter
450             try
451             {
452                 Object quickStarter = msf.createInstance("com.sun.star.office.Quickstart");
453                 XFastPropertySet fps = UnoRuntime.queryInterface(XFastPropertySet.class, quickStarter);
454                 fps.setFastPropertyValue(0, false);
455             }
456             catch (com.sun.star.uno.Exception ex)
457             {
458                 dbg("ERROR: Could not disable QuickStarter: " + ex.toString());
459             }
460 
461             try
462             {
463                 desk = UnoRuntime.queryInterface(XDesktop.class, msf.createInstance("com.sun.star.frame.Desktop"));
464                 msf = null;
465 
466                 if (desk != null)
467                 {
468                     final boolean allClosed = closeAllWindows(desk);
469 
470                     if (!allClosed)
471                     {
472                         dbg("Couldn't close all office windows!");
473                     }
474 
475                     dbg("Trying to terminate the desktop");
476 
477                     desk.terminate();
478                     dbg("Desktop terminated");
479                 }
480             }
481             catch (com.sun.star.uno.Exception ue)
482             {
483                 result = false;
484             }
485             catch (com.sun.star.lang.DisposedException ue)
486             {
487                 result = false;
488             }
489         }
490 
491         final ProcessHandler ph = (ProcessHandler) param.get("AppProvider");
492 
493         if (ph != null)
494         {
495             // dispose watcher in case it's still running.
496             final OfficeWatcher ow = (OfficeWatcher) param.get("Watcher");
497 
498             if ((ow != null) && ow.isAlive())
499             {
500                 ow.finish = true;
501             }
502 
503             ph.kill();
504         }
505 
506         param.remove("AppProvider");
507         param.remove("ServiceFactory");
508 
509         //copy user_backup into user layer
510         try
511         {
512             final String userLayer = (String) param.get("userLayer");
513             final String copyLayer = (String) param.get("copyLayer");
514             if (userLayer != null && copyLayer != null)
515             {
516                 deleteFilesAndDirector(new File(userLayer));
517                 final File copyFile = new File(copyLayer);
518                 dbg("copy '" + copyFile + "' -> '" + userLayer + "'");
519                 FileTools.copyDirectory(copyFile, new File(userLayer), new String[]
520                         {
521                             "temp"
522                         });
523                 dbg("copy '" + copyFile + "' -> '" + userLayer + "' finished");
524 
525             // remove all user_backup folder in temp dir
526             // this is for the case the runner was killed and some old backup folder still stay in temp dir
527 
528 
529             }
530             else
531             {
532                 System.out.println("Cannot copy layer: '" + copyLayer + "' back to user layer: '" + userLayer + "'");
533             }
534         }
535         catch (java.io.IOException e)
536         {
537             dbg("Couldn't recover from backup\n" + e.getMessage());
538         }
539         return result;
540     }
541 
closeAllWindows(XDesktop desk)542     private boolean closeAllWindows(XDesktop desk)
543     {
544         final XEnumerationAccess compEnumAccess = desk.getComponents();
545         final XEnumeration compEnum = compEnumAccess.createEnumeration();
546         boolean res = true;
547 
548         try
549         {
550             while (compEnum.hasMoreElements())
551             {
552                 final XCloseable closer = UnoRuntime.queryInterface(XCloseable.class, compEnum.nextElement());
553 
554                 if (closer != null)
555                 {
556                     closer.close(true);
557                 }
558             }
559         }
560         catch (com.sun.star.util.CloseVetoException cve)
561         {
562             res = false;
563         }
564         catch (com.sun.star.container.NoSuchElementException nsee)
565         {
566             res = false;
567         }
568         catch (com.sun.star.lang.WrappedTargetException wte)
569         {
570             res = false;
571         }
572 
573         return res;
574     }
575 
createStringSubstitution(XMultiServiceFactory xMSF)576     private static XStringSubstitution createStringSubstitution(XMultiServiceFactory xMSF) throws com.sun.star.uno.Exception
577     {
578         Object xPathSubst = xMSF.createInstance(
579                     "com.sun.star.util.PathSubstitution");
580         return UnoRuntime.queryInterface(XStringSubstitution.class, xPathSubst);
581     }
582 
583     /**
584      * converts directory without 'file:///' prefix.
585      * and System dependent file separator
586      */
getDirSys(String dir)587     private static String getDirSys(String dir)
588     {
589         String sysDir = "";
590 
591         final int idx = dir.indexOf("file://");
592 
593         final int idx2 = dir.indexOf("file:///");
594 
595         // remove leading 'file://'
596         if (idx < 0)
597         {
598             sysDir = dir;
599         }
600         else
601         {
602             sysDir = dir.substring("file://".length());
603         }
604 
605         sysDir = sysDir.replace("%20", " ");
606 
607         // append '/' if not there (e.g. linux)
608         if (sysDir.charAt(sysDir.length() - 1) != '/')
609         {
610             sysDir += "/";
611         }
612 
613         // remove leading '/' and replace others with '\' on windows machines
614         final String sep = System.getProperty("file.separator");
615 
616         if (sep.equalsIgnoreCase("\\"))
617         {
618             if (idx2 >= 0)
619             {
620                 sysDir = sysDir.substring(1);
621             }
622             else
623             {
624                 //network path
625                 sysDir = "//" + sysDir;
626             }
627             sysDir = sysDir.replace('/', '\\');
628         }
629 
630         return sysDir;
631     }
632 
633     /**
634      * If the office is connected but the <CODE>AppExecutionCommand</CODE> is not set,
635      * this function asks the office for its location and fill the
636      * <CODE>AppExecutionCommand</CODE> with valid content.
637      * This function was only called if parameter <CODE>AutoRestart</CODE> is set.
638      * @param msf the <CODE>MultiServiceFactory</CODE>
639      * @param param the <CODE>TestParameters</CODE>
640      */
makeAppExecCommand(XMultiServiceFactory msf, TestParameters param)641     private void makeAppExecCommand(XMultiServiceFactory msf, TestParameters param)
642     {
643         debug = param.getBool(PropertyName.DEBUG_IS_ACTIVE);
644 
645         // get existing AppExecutionCommand if available, else empty string
646         String command = (String) param.get(util.PropertyName.APP_EXECUTION_COMMAND);
647 
648         String connectionString;
649         if (param.getBool(util.PropertyName.USE_PIPE_CONNECTION))
650         {
651             // This is the default behaviour
652             connectionString = (String) param.get(util.PropertyName.PIPE_CONNECTION_STRING);
653         }
654         else
655         {
656             // is used if UsePipeConnection=false
657             connectionString = (String) param.get(util.PropertyName.CONNECTION_STRING);
658         }
659 
660         String sysBinDir = "";
661 
662         try
663         {
664             sysBinDir = utils.getSystemURL(utils.expandMacro(msf, "$SYSBINDIR"));
665         }
666         catch (java.lang.Exception e)
667         {
668             dbg("could not get system binary directory");
669             return;
670         }
671 
672         // does the existing command show to the connected office?
673         if (command.indexOf(sysBinDir) == -1)
674         {
675             command = sysBinDir + System.getProperty("file.separator") + "soffice" +
676                     " --norestore --accept=" + connectionString + ";urp;";
677         }
678 
679         dbg("update AppExecutionCommand: " + command);
680 
681         param.put(util.PropertyName.APP_EXECUTION_COMMAND, command);
682     }
683 
dbg(String message)684     private void dbg(String message)
685     {
686         if (debug)
687         {
688             System.out.println(utils.getDateTime() + "OfficeProvider: " + message);
689         }
690 
691     }
692 
693     private static class OfficeWatcherPing extends Thread
694     {
695 
696         private final OfficeWatcher ow;
697         private boolean bStop = false;
698 
OfficeWatcherPing(OfficeWatcher ow)699         public OfficeWatcherPing(OfficeWatcher ow)
700         {
701             this.ow = ow;
702         }
703 
704         @Override
run()705         public void run()
706         {
707             System.out.println(utils.getDateTime() + "OfficeProvider:Owp: start ");
708 
709             while (!bStop)
710             {
711                 System.out.println(utils.getDateTime() + "OfficeProvider:Owp: ping ");
712                 ow.ping();
713                 System.out.println(utils.getDateTime() + "OfficeProvider:Owp: sleep ");
714                 util.utils.pause(1000);
715             }
716 
717         }
718 
finish()719         public void finish()
720         {
721             synchronized(this)
722             {
723                 bStop = true;
724                 System.out.println(utils.getDateTime() + "OfficeProvider:Owp: stop ");
725 
726                 notify();
727             }
728         }
729     }
730 
deleteFilesAndDirector(File file)731     private void deleteFilesAndDirector(File file)
732     {
733         File f = file;
734         if(f.isDirectory())
735         {
736             File files[] = f.listFiles();
737             int filesLength = files != null ? files.length : 0;
738             for(int i = 0; i < filesLength; ++i)
739             {
740                 deleteFilesAndDirector(files[i]);
741             }
742             boolean bDeleteOk = f.delete();
743             if (!bDeleteOk) {
744                 System.out.println("delete failed");
745             }
746         }
747         else if (f.isFile())
748         {
749             boolean bDeleteOk = f.delete();
750             if (!bDeleteOk) {
751                 System.out.println("delete failed");
752             }
753         }
754     }
755 }
756