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