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