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