/* * 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 complex.framework.autosave; import com.sun.star.beans.PropertyValue; import com.sun.star.frame.FeatureStateEvent; import com.sun.star.frame.XDispatch; import com.sun.star.frame.XDispatchProvider; import com.sun.star.frame.XModel; import com.sun.star.frame.XStatusListener; import com.sun.star.frame.theAutoRecovery; import com.sun.star.lang.XMultiServiceFactory; import com.sun.star.sheet.FillDirection; import com.sun.star.sheet.XCellSeries; import com.sun.star.table.XCellRange; import com.sun.star.util.XCloseable; import com.sun.star.sheet.XSpreadsheet; import com.sun.star.sheet.XSpreadsheetDocument; import com.sun.star.sheet.XSpreadsheets; import com.sun.star.uno.AnyConverter; import com.sun.star.uno.Type; import com.sun.star.uno.UnoRuntime; import com.sun.star.uno.XInterface; import com.sun.star.util.URL; import com.sun.star.util.XURLTransformer; import java.util.*; import util.utils; // ---------- junit imports ----------------- import org.junit.After; import org.junit.AfterClass; import org.junit.Before; import org.junit.BeforeClass; import org.junit.Test; import org.openoffice.test.OfficeConnection; import util.SOfficeFactory; import static org.junit.Assert.*; /** @short Check some use cases of the AutoSave feature */ public class AutoSave { private static class AutoSaveListener implements XStatusListener { private XDispatch m_xAutoSave; private URL m_aRegistration; private final Protocol m_aLog; private AutoSaveListener(XMultiServiceFactory xSMGR , XDispatch xAutoSave, Protocol aLog ) { m_aLog = aLog; m_aLog.log(Protocol.TYPE_SCOPE_OPEN, "create listener for AutoSave notifications ..."); try { m_xAutoSave = xAutoSave; XURLTransformer xParser = UnoRuntime.queryInterface(XURLTransformer.class, xSMGR.createInstance("com.sun.star.util.URLTransformer")); URL[] aURL = new URL[1]; aURL[0] = new URL(); aURL[0].Complete = "vnd.sun.star.autorecovery:/doAutoSave"; xParser.parseStrict(aURL); m_aRegistration = aURL[0]; m_xAutoSave.addStatusListener(this, m_aRegistration); m_aLog.log(Protocol.TYPE_INFO, "successfully registered as AutoSave listener."); } catch(Throwable ex) { m_aLog.log(ex); } m_aLog.log(Protocol.TYPE_SCOPE_CLOSE, ""); } private void disableListener() { m_aLog.log(Protocol.TYPE_SCOPE_OPEN, "stop listening for AutoSave notifications ..."); XDispatch xAutoSave = null; URL aRegURL = null; synchronized (this) { xAutoSave = m_xAutoSave; aRegURL = m_aRegistration; } try { if ( (xAutoSave != null) && (aRegURL != null) ) xAutoSave.removeStatusListener(this, aRegURL); } catch(Throwable ex) { m_aLog.log(ex); } m_aLog.log(Protocol.TYPE_SCOPE_CLOSE, ""); } public void statusChanged(FeatureStateEvent aEvent) { m_aLog.log(Protocol.TYPE_SCOPE_OPEN, "statusChanged() called from AutoSave ..."); m_aLog.log("FeatureURL = \""+aEvent.FeatureURL.Complete+"\"" ); m_aLog.log("FeatureDescriptor = \""+aEvent.FeatureDescriptor+"\"" ); m_aLog.log("IsEnabled = \""+aEvent.IsEnabled+"\"" ); m_aLog.log("Requery = \""+aEvent.Requery+"\"" ); m_aLog.log("State:" ); m_aLog.log(aEvent.State ); m_aLog.log(Protocol.TYPE_SCOPE_CLOSE, ""); } public void disposing(com.sun.star.lang.EventObject aEvent) { m_aLog.log(Protocol.TYPE_INFO, "disposing() called from AutoSave."); synchronized(this) { m_xAutoSave = null; m_aRegistration = null; } } } // some const // member private Protocol m_aLog; /** points to the global uno service manager. */ private XMultiServiceFactory m_xSMGR = null; private SOfficeFactory m_aSOF; /** can be used to trigger/enable/disable the AutoSave feature. */ private XDispatch m_xAutoSave = null; private XURLTransformer m_xURLParser = null; // test environment /** @short Create the environment for following tests. @descr create an empty test frame, where we can load different components inside. */ @Before public void before() { m_aLog = new Protocol(Protocol.MODE_HTML | Protocol.MODE_STDOUT, Protocol.FILTER_NONE, utils.getUsersTempDir() + "/complex_log_ascii_01.html"); try { // get uno service manager from global test environment m_xSMGR = getMSF(); // get another helper to e.g. create test documents m_aSOF = SOfficeFactory.getFactory(m_xSMGR); // create AutoSave instance m_xAutoSave = theAutoRecovery.get(connection.getComponentContext()); // prepare AutoSave // make sure it will be started every 1 min ConfigHelper aConfig = new ConfigHelper(connection.getComponentContext(), "org.openoffice.Office.Recovery", false); aConfig.writeRelativeKey("AutoSave", "Enabled" , Boolean.TRUE ); aConfig.writeRelativeKey("AutoSave", "TimeIntervall", Integer.valueOf(1)); // 1 min aConfig.flush(); aConfig = null; // is needed to parse dispatch commands m_xURLParser = UnoRuntime.queryInterface(XURLTransformer.class, m_xSMGR.createInstance("com.sun.star.util.URLTransformer")); } catch(java.lang.Throwable ex) { m_aLog.log(ex); fail("Couldn't create test environment"); } } /** @short close the environment. */ @After public void after() { // ??? } // create a calc document with content, which needs some time for saving private XInterface createBigCalcDoc() { m_aLog.log(Protocol.TYPE_SCOPE_OPEN, "createBigCalcDoc() started ..."); try { m_aLog.log("Create empty calc document for testing."); XSpreadsheetDocument xSheetDoc = m_aSOF.createCalcDoc("_default"); m_aLog.log("Retrieve first sheet from calc document."); XSpreadsheets xSheets = xSheetDoc.getSheets(); XSpreadsheet xSheet = (XSpreadsheet)AnyConverter.toObject( new Type(XSpreadsheet.class), xSheets.getByName( xSheets.getElementNames()[0])); m_aLog.log("Fill two cells with value and formula."); xSheet.getCellByPosition(0, 0).setValue(1); xSheet.getCellByPosition(0, 1).setFormula("=a1+1"); m_aLog.log("Retrieve big range."); XCellRange xRange = xSheet.getCellRangeByName("A1:Z9999"); XCellSeries xSeries = UnoRuntime.queryInterface(XCellSeries.class, xRange); m_aLog.log("Duplicate cells from top to bottom inside range."); xSeries.fillAuto(FillDirection.TO_BOTTOM, 2); m_aLog.log("Duplicate cells from left to right inside range."); xSeries.fillAuto(FillDirection.TO_RIGHT , 1); m_aLog.log(Protocol.TYPE_SCOPE_CLOSE | Protocol.TYPE_OK, "createBigCalcDoc() finished."); return xSheetDoc; } catch(Throwable ex) { m_aLog.log(ex); } m_aLog.log(Protocol.TYPE_SCOPE_CLOSE, "createBigCalcDoc() finished."); return null; } private void saveDoc(XInterface xDoc, String sURL) { m_aLog.log(Protocol.TYPE_SCOPE_OPEN, "saveDoc('"+sURL+"') started ..."); try { URL[] aURL = new URL[1]; aURL[0] = new URL(); aURL[0].Complete = ".uno:SaveAs"; m_xURLParser.parseStrict(aURL); XModel xModel = UnoRuntime.queryInterface(XModel.class, xDoc); XDispatchProvider xProvider = UnoRuntime.queryInterface(XDispatchProvider.class, xModel.getCurrentController()); XDispatch xDispatch = xProvider.queryDispatch(aURL[0], "_self", 0); PropertyValue[] lArgs = new PropertyValue[3]; lArgs[0] = new PropertyValue(); lArgs[0].Name = "URL"; lArgs[0].Value = sURL; lArgs[1] = new PropertyValue(); lArgs[1].Name = "Overwrite"; lArgs[1].Value = Boolean.TRUE; lArgs[2] = new PropertyValue(); lArgs[2].Name = "StoreTo"; lArgs[2].Value = Boolean.TRUE; xDispatch.dispatch(aURL[0], lArgs); m_aLog.log(Protocol.TYPE_OK, "saveDoc('"+sURL+"') = OK."); } catch(Throwable ex) { m_aLog.log(ex); } m_aLog.log(Protocol.TYPE_SCOPE_CLOSE, "saveDoc('"+sURL+"') finished."); } private void closeDoc(XInterface xDoc) { m_aLog.log(Protocol.TYPE_SCOPE_OPEN, "closeDoc() started ..."); try { Random aRandom = new Random(); int nRetry = 5; while(nRetry>0) { try { XCloseable xClose = UnoRuntime.queryInterface(XCloseable.class, xDoc); if (xClose != null) { xClose.close(false); m_aLog.log(Protocol.TYPE_OK, "closeDoc() = OK."); nRetry = 0; } else { m_aLog.log(Protocol.TYPE_ERROR, "closeDoc() = ERROR. Doc doesn't provide needed interface!"); } } catch(com.sun.star.util.CloseVetoException exVeto) { m_aLog.log(Protocol.TYPE_WARNING , "got CloseVetoException on calling doc.close()." ); m_aLog.log(Protocol.TYPE_WARNING_INFO, "Please check the reason for that more in detail." ); m_aLog.log(Protocol.TYPE_WARNING_INFO, "A message like \"Can not close while saving.\" was intended and doesn't show an error!"); m_aLog.log(Protocol.TYPE_WARNING_INFO, exVeto.getMessage()); } if (nRetry > 0) { --nRetry; long nWait = aRandom.nextInt(30000); // 30 sec. try { m_aLog.log(Protocol.TYPE_INFO, "sleep for "+nWait+" ms"); synchronized(this) { wait(nWait); } } catch(Throwable ex) { m_aLog.log(Protocol.TYPE_WARNING , "got exception for wait() !?"); m_aLog.log(Protocol.TYPE_WARNING_INFO, ex.getMessage()); } } } } catch(Throwable ex) { m_aLog.log(ex); } m_aLog.log(Protocol.TYPE_SCOPE_CLOSE, "closeDoc() finished."); } private class DocThread extends Thread { DocThread() {} @Override public void run() { impl_checkConcurrentAutoSaveToNormalUISave(); } } /** @short check concurrent save requests to the same document * at the same time. * * @descr First we simulate an UI save by dispatching the right URL * to the document and at the same time we try to trigger an AutoSave * from another thread. So these operations should be started at the same time. * It should not crash. The AutoSave request must be postponed. */ @Test public void checkConcurrentAutoSaveToNormalUISave() { m_aLog.log(Protocol.TYPE_TESTMARK , "AutoSave"); m_aLog.log(Protocol.TYPE_SCOPE_OPEN, "checkConcurrentAutoSaveToNormalUISave()"); AutoSaveListener xListener = new AutoSaveListener(m_xSMGR, m_xAutoSave, m_aLog); try { DocThread aThread = new DocThread(); aThread.start(); aThread.join(); } catch(Throwable ex) {} xListener.disableListener(); m_aLog.log(Protocol.TYPE_SCOPE_CLOSE, "checkConcurrentAutoSaveToNormalUISave()"); m_aLog.logStatistics(); } public void impl_checkConcurrentAutoSaveToNormalUISave() { Random aRandom = new Random(); int i = 0; int c = 5; for (i=0; i