/* * This file is part of the LibreOffice project. * * This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. * * This file incorporates work covered by the following license notice: * * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed * with this work for additional information regarding copyright * ownership. The ASF licenses this file to you under the Apache * License, Version 2.0 (the "License"); you may not use this file * except in compliance with the License. You may obtain a copy of * the License at http://www.apache.org/licenses/LICENSE-2.0 . */ package helper; import com.sun.star.beans.XFastPropertySet; import com.sun.star.bridge.XUnoUrlResolver; import com.sun.star.container.XEnumeration; import com.sun.star.container.XEnumerationAccess; import com.sun.star.frame.XDesktop; import com.sun.star.lang.XMultiComponentFactory; import com.sun.star.lang.XMultiServiceFactory; import com.sun.star.uno.UnoRuntime; import com.sun.star.uno.XComponentContext; import com.sun.star.util.XCloseable; import com.sun.star.util.XStringSubstitution; import java.io.File; import java.io.PrintWriter; import java.io.UnsupportedEncodingException; import lib.TestParameters; import share.DescEntry; import share.LogWriter; import util.DynamicClassLoader; import util.PropertyName; import util.utils; /** * This class will connect the office and start it if possible * */ public class OfficeProvider implements AppProvider { private boolean debug = false; /** * copy the user layer to a safe place, usually to $TMP/user_backup$USER */ private void backupUserLayer(TestParameters param, XMultiServiceFactory msf) { try { final XStringSubstitution sts = createStringSubstitution(msf); debug = param.getBool(PropertyName.DEBUG_IS_ACTIVE); String userLayer = sts.getSubstituteVariableValue("$(user)"); userLayer = getDirSys(userLayer); param.put("userLayer", userLayer); final String copyLayer = util.utils.getUsersTempDir() + System.getProperty("file.separator") + "user_backup" + System.getProperty("user.name"); param.put("copyLayer", copyLayer); dbg(" copy '" + userLayer + "' ->" + copyLayer + "'"); // Slow machines the copy job could spend some time. To avoid activating of OfficeWatcher it must be pinged OfficeWatcherPing owp = new OfficeWatcherPing((OfficeWatcher) param.get(PropertyName.OFFICE_WATCHER)); owp.start(); deleteFilesAndDirector (new File(copyLayer)); FileTools.copyDirectory(new File(userLayer), new File(copyLayer), new String[] { "temp" }); owp.finish(); } catch (com.sun.star.container.NoSuchElementException e) { System.out.println("User Variable '$(user)' not defined."); } catch (com.sun.star.uno.Exception e) { System.out.println("Couldn't backup user layer"); e.printStackTrace(); } catch (java.io.IOException e) { System.out.println("Couldn't backup user layer"); e.printStackTrace(); } } /** * Dispose the office. * This method can only be used, if the office was connected in the first * place: getManager() was called first. * @return return true if desktop is terminates, else false */ public boolean disposeManager(lib.TestParameters param) { XMultiServiceFactory msf = param.getMSF(); if (msf == null) { return true; } else { XDesktop desk = null; try { desk = UnoRuntime.queryInterface(XDesktop.class, msf.createInstance("com.sun.star.frame.Desktop")); } catch (com.sun.star.uno.Exception ue) { return false; } msf = null; if (desk != null) { desk.terminate(); return true; } else { return false; } } } /** * Method to get the ServiceManager of an Office */ public Object getManager(lib.TestParameters param) throws UnsupportedEncodingException { String errorMessage = null; boolean bAppExecutionHasWarning = false; debug = param.getBool(PropertyName.DEBUG_IS_ACTIVE); String additionalArgs = (String) param.get( "AdditionalConnectionArguments"); if (additionalArgs == null) { additionalArgs = ";"; } else { additionalArgs = "," + additionalArgs + ";"; } final String cncstr = "uno:" + param.get("ConnectionString") + ";urp" + additionalArgs + "StarOffice.ServiceManager"; System.out.println("Connecting the Office with " + cncstr); XMultiServiceFactory msf = connectOffice(cncstr); // if the office is running and the office crashes while testing it could // be useful to restart the office if possible and continuing the tests. // Example: the UNO-API-Tests in the projects will be executed by calling // 'damke'. This connects to an existing office. If the office crashes // it is useful to restart the office and continuing the tests. if ((param.getBool(util.PropertyName.AUTO_RESTART)) && (msf != null)) { makeAppExecCommand(msf, param); } if (msf == null) { String exc = ""; Exception exConnectFailed = null; boolean isExecutable = false; boolean isAppKnown = ((cncstr.indexOf("host=localhost") > 0) || (cncstr.indexOf("pipe,name=") > 0)); isAppKnown &= ((String) param.get("AppExecutionCommand")).length() != 0; if (isAppKnown) { dbg("Local Connection trying to start the Office"); //ensure that a pending officewatcher gets finished before a new //office is started final OfficeWatcher ow_old = (OfficeWatcher) param.get("Watcher"); if (ow_old != null) { ow_old.finish = true; } final String cmd = (String) param.get("AppExecutionCommand"); dbg("AppExecutionCommand: " + cmd); // validate the AppExecutionCommand, but try it out anyway. // keep the error message for later. errorMessage = util.utils.validateAppExecutionCommand(cmd, (String) param.get("OperatingSystem")); if (errorMessage.startsWith("Error")) { System.out.println(errorMessage); return null; } bAppExecutionHasWarning = !errorMessage.equals("OK"); final DynamicClassLoader dcl = new DynamicClassLoader(); final LogWriter log = (LogWriter) dcl.getInstance( (String) param.get("LogWriter")); //create empty entry final DescEntry Entry = new DescEntry(); Entry.entryName = "office"; Entry.longName = "office"; Entry.EntryType = "placebo"; Entry.isOptional = false; Entry.isToTest = false; Entry.SubEntryCount = 0; Entry.hasErrorMsg = false; Entry.State = "non possible"; Entry.UserDefinedParams = param; log.initialize(Entry, debug); final ProcessHandler ph = new ProcessHandler(cmd, (PrintWriter) log); isExecutable = ph.executeAsynchronously(); if (isExecutable) { param.put("AppProvider", ph); final OfficeWatcher ow = new OfficeWatcher(param); param.put("Watcher", ow); ow.start(); ow.ping(); } int k = 0; // wait up to 21 seconds to get an office connection while ((k < 42) && (msf == null)) { try { msf = connect(cncstr); } catch (com.sun.star.uno.Exception ue) { exConnectFailed = ue; exc = ue.getMessage(); } catch (java.lang.Exception je) { exConnectFailed = je; exc = je.getMessage(); } if (msf == null) { util.utils.pause(500 * k); } k++; } if (msf == null) { System.out.println("Exception while connecting.\n" + exConnectFailed); if (exc != null) { System.out.println(exc); } if (bAppExecutionHasWarning) { System.out.println(errorMessage); } } else if (isExecutable) { backupUserLayer(param, msf); } } else { System.out.println("Could not connect an Office and cannot start one.\n".concat("please start an office with following parameter:\n"). concat("\nsoffice --accept=").concat((String) param.get("ConnectionString")).concat(";urp;\n")); } } return msf; } /** * Connect an Office * @param connectStr * @return * @throws com.sun.star.uno.Exception * @throws com.sun.star.uno.RuntimeException * @throws com.sun.star.connection.NoConnectException * @throws Exception */ private XMultiServiceFactory connect(String connectStr) throws com.sun.star.uno.Exception, com.sun.star.uno.RuntimeException, com.sun.star.connection.NoConnectException, Exception { // Get component context final XComponentContext xcomponentcontext = com.sun.star.comp.helper.Bootstrap.createInitialComponentContext(null); // initial serviceManager final XMultiComponentFactory xLocalServiceManager = xcomponentcontext.getServiceManager(); // create a connector, so that it can contact the office final Object xUrlResolver = xLocalServiceManager.createInstanceWithContext("com.sun.star.bridge.UnoUrlResolver", xcomponentcontext); final XUnoUrlResolver urlResolver = UnoRuntime.queryInterface(XUnoUrlResolver.class, xUrlResolver); final Object rInitialObject = urlResolver.resolve(connectStr); XMultiServiceFactory xMSF = null; if (rInitialObject != null) { // debug = true; dbg("resolved url"); xMSF = UnoRuntime.queryInterface(XMultiServiceFactory.class, rInitialObject); } return xMSF; } /** * Close an office. * @param param The test parameters. * @param closeIfPossible If true, close even if * it was running before the test */ public boolean closeExistingOffice(lib.TestParameters param, boolean closeIfPossible) { XMultiServiceFactory msf = param.getMSF(); final boolean alreadyConnected = (msf != null); debug = param.getBool(PropertyName.DEBUG_IS_ACTIVE); if (alreadyConnected) { dbg("try to get ProcessHandler"); final ProcessHandler ph = (ProcessHandler) param.get("AppProvider"); if (ph != null) { dbg("ProcessHandler != null"); disposeOffice(msf, param); // dispose watcher in case it's still running. dbg("try to get OfficeWatcher"); final OfficeWatcher ow = (OfficeWatcher) param.get("Watcher"); if ((ow != null) && ow.isAlive()) { dbg("OfficeWatcher will be finished"); ow.finish = true; } else { dbg("OfficeWatcher seems to be finished"); } return true; } else { if (closeIfPossible) { return disposeOffice(msf, param); } } } else { final String cncstr = "uno:" + param.get("ConnectionString") + ";urp;StarOffice.ServiceManager"; dbg("try to connect office"); msf = connectOffice(cncstr); if (closeIfPossible) { return disposeOffice(msf, param); } } dbg("closeExistingOffice finished"); return true; } private XMultiServiceFactory connectOffice(String cncstr) { XMultiServiceFactory msf = null; String exc = ""; // debug = true; dbg("trying to connect to " + cncstr); try { msf = connect(cncstr); } catch (com.sun.star.uno.Exception ue) { exc = ue.getMessage(); } catch (java.lang.Exception je) { exc = je.getMessage(); } if (debug && exc != null && exc.length() != 0) { dbg("Could not connect an Office. " + exc); } return msf; } private boolean disposeOffice(XMultiServiceFactory msf, TestParameters param) { XDesktop desk = null; debug = param.getBool(PropertyName.DEBUG_IS_ACTIVE); boolean result = true; if (msf != null) { // disable QuickStarter try { Object quickStarter = msf.createInstance("com.sun.star.office.Quickstart"); XFastPropertySet fps = UnoRuntime.queryInterface(XFastPropertySet.class, quickStarter); fps.setFastPropertyValue(0, false); } catch (com.sun.star.uno.Exception ex) { dbg("ERROR: Could not disable QuickStarter: " + ex.toString()); } try { desk = UnoRuntime.queryInterface(XDesktop.class, msf.createInstance("com.sun.star.frame.Desktop")); msf = null; if (desk != null) { final boolean allClosed = closeAllWindows(desk); if (!allClosed) { dbg("Couldn't close all office windows!"); } dbg("Trying to terminate the desktop"); desk.terminate(); dbg("Desktop terminated"); } } catch (com.sun.star.uno.Exception ue) { result = false; } catch (com.sun.star.lang.DisposedException ue) { result = false; } } final ProcessHandler ph = (ProcessHandler) param.get("AppProvider"); if (ph != null) { // dispose watcher in case it's still running. final OfficeWatcher ow = (OfficeWatcher) param.get("Watcher"); if ((ow != null) && ow.isAlive()) { ow.finish = true; } ph.kill(); } param.remove("AppProvider"); param.remove("ServiceFactory"); //copy user_backup into user layer try { final String userLayer = (String) param.get("userLayer"); final String copyLayer = (String) param.get("copyLayer"); if (userLayer != null && copyLayer != null) { deleteFilesAndDirector(new File(userLayer)); final File copyFile = new File(copyLayer); dbg("copy '" + copyFile + "' -> '" + userLayer + "'"); FileTools.copyDirectory(copyFile, new File(userLayer), new String[] { "temp" }); dbg("copy '" + copyFile + "' -> '" + userLayer + "' finished"); // remove all user_backup folder in temp dir // this is for the case the runner was killed and some old backup folder still stay in temp dir } else { System.out.println("Cannot copy layer: '" + copyLayer + "' back to user layer: '" + userLayer + "'"); } } catch (java.io.IOException e) { dbg("Couldn't recover from backup\n" + e.getMessage()); } return result; } private boolean closeAllWindows(XDesktop desk) { final XEnumerationAccess compEnumAccess = desk.getComponents(); final XEnumeration compEnum = compEnumAccess.createEnumeration(); boolean res = true; try { while (compEnum.hasMoreElements()) { final XCloseable closer = UnoRuntime.queryInterface(XCloseable.class, compEnum.nextElement()); if (closer != null) { closer.close(true); } } } catch (com.sun.star.util.CloseVetoException cve) { res = false; } catch (com.sun.star.container.NoSuchElementException nsee) { res = false; } catch (com.sun.star.lang.WrappedTargetException wte) { res = false; } return res; } private static XStringSubstitution createStringSubstitution(XMultiServiceFactory xMSF) throws com.sun.star.uno.Exception { Object xPathSubst = xMSF.createInstance( "com.sun.star.util.PathSubstitution"); return UnoRuntime.queryInterface(XStringSubstitution.class, xPathSubst); } /** * converts directory without 'file:///' prefix. * and System dependent file separator */ private static String getDirSys(String dir) { String sysDir = ""; final int idx = dir.indexOf("file://"); final int idx2 = dir.indexOf("file:///"); // remove leading 'file://' if (idx < 0) { sysDir = dir; } else { sysDir = dir.substring("file://".length()); } sysDir = sysDir.replace("%20", " "); // append '/' if not there (e.g. linux) if (sysDir.charAt(sysDir.length() - 1) != '/') { sysDir += "/"; } // remove leading '/' and replace others with '\' on windows machines final String sep = System.getProperty("file.separator"); if (sep.equalsIgnoreCase("\\")) { if (idx2 >= 0) { sysDir = sysDir.substring(1); } else { //network path sysDir = "//" + sysDir; } sysDir = sysDir.replace('/', '\\'); } return sysDir; } /** * If the office is connected but the AppExecutionCommand is not set, * this function asks the office for its location and fill the * AppExecutionCommand with valid content. * This function was only called if parameter AutoRestart is set. * @param msf the MultiServiceFactory * @param param the TestParameters */ private void makeAppExecCommand(XMultiServiceFactory msf, TestParameters param) { debug = param.getBool(PropertyName.DEBUG_IS_ACTIVE); // get existing AppExecutionCommand if available, else empty string String command = (String) param.get(util.PropertyName.APP_EXECUTION_COMMAND); String connectionString; if (param.getBool(util.PropertyName.USE_PIPE_CONNECTION)) { // This is the default behaviour connectionString = (String) param.get(util.PropertyName.PIPE_CONNECTION_STRING); } else { // is used if UsePipeConnection=false connectionString = (String) param.get(util.PropertyName.CONNECTION_STRING); } String sysBinDir = ""; try { sysBinDir = utils.getSystemURL(utils.expandMacro(msf, "$SYSBINDIR")); } catch (java.lang.Exception e) { dbg("could not get system binary directory"); return; } // does the existing command show to the connected office? if (command.indexOf(sysBinDir) == -1) { command = sysBinDir + System.getProperty("file.separator") + "soffice" + " --norestore --accept=" + connectionString + ";urp;"; } dbg("update AppExecutionCommand: " + command); param.put(util.PropertyName.APP_EXECUTION_COMMAND, command); } private void dbg(String message) { if (debug) { System.out.println(utils.getDateTime() + "OfficeProvider: " + message); } } private static class OfficeWatcherPing extends Thread { private final OfficeWatcher ow; private boolean bStop = false; public OfficeWatcherPing(OfficeWatcher ow) { this.ow = ow; } @Override public void run() { System.out.println(utils.getDateTime() + "OfficeProvider:Owp: start "); while (!bStop) { System.out.println(utils.getDateTime() + "OfficeProvider:Owp: ping "); ow.ping(); System.out.println(utils.getDateTime() + "OfficeProvider:Owp: sleep "); util.utils.pause(1000); } } public void finish() { synchronized(this) { bStop = true; System.out.println(utils.getDateTime() + "OfficeProvider:Owp: stop "); notify(); } } } private void deleteFilesAndDirector(File file) { File f = file; if(f.isDirectory()) { File files[] = f.listFiles(); int filesLength = files != null ? files.length : 0; for(int i = 0; i < filesLength; ++i) { deleteFilesAndDirector(files[i]); } boolean bDeleteOk = f.delete(); if (!bDeleteOk) { System.out.println("delete failed"); } } else if (f.isFile()) { boolean bDeleteOk = f.delete(); if (!bDeleteOk) { System.out.println("delete failed"); } } } }