/************************************************************************* * * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * Copyright 2000, 2010 Oracle and/or its affiliates. * * OpenOffice.org - a multi-platform office productivity suite * * This file is part of OpenOffice.org. * * OpenOffice.org is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License version 3 * only, as published by the Free Software Foundation. * * OpenOffice.org is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License version 3 for more details * (a copy is included in the LICENSE file that accompanied this code). * * You should have received a copy of the GNU Lesser General Public License * version 3 along with OpenOffice.org. If not, see * * for a copy of the LGPLv3 License. * ************************************************************************/ package complex.dbaccess; import com.sun.star.awt.XTopWindow; import com.sun.star.beans.PropertyState; import com.sun.star.document.DocumentEvent; import com.sun.star.lang.XEventListener; import com.sun.star.lang.XMultiServiceFactory; import com.sun.star.script.XStorageBasedLibraryContainer; import com.sun.star.task.XInteractionRequest; import com.sun.star.uno.Type; import com.sun.star.uno.UnoRuntime; import com.sun.star.frame.XStorable; import com.sun.star.beans.PropertyValue; import com.sun.star.beans.XPropertySet; import com.sun.star.container.XNameContainer; import com.sun.star.container.XSet; import com.sun.star.document.XDocumentEventBroadcaster; import com.sun.star.document.XDocumentEventListener; import com.sun.star.document.XEmbeddedScripts; import com.sun.star.document.XEventsSupplier; import com.sun.star.lang.XComponent; import com.sun.star.frame.XComponentLoader; import com.sun.star.frame.XDispatch; import com.sun.star.frame.XDispatchProvider; import com.sun.star.frame.XFrame; import com.sun.star.frame.XLoadable; import com.sun.star.frame.XModel; import com.sun.star.frame.XModel2; import com.sun.star.frame.XTitle; import com.sun.star.lang.EventObject; import com.sun.star.lang.XServiceInfo; import com.sun.star.lang.XSingleComponentFactory; import com.sun.star.lang.XTypeProvider; import com.sun.star.script.provider.XScriptProviderSupplier; import com.sun.star.sdb.XDocumentDataSource; import com.sun.star.sdb.XFormDocumentsSupplier; import com.sun.star.sdb.XOfficeDatabaseDocument; import com.sun.star.sdb.XReportDocumentsSupplier; import com.sun.star.task.DocumentMacroConfirmationRequest; import com.sun.star.task.XInteractionApprove; import com.sun.star.task.XInteractionContinuation; import com.sun.star.task.XInteractionHandler; import com.sun.star.uno.XComponentContext; import com.sun.star.util.CloseVetoException; import com.sun.star.util.URL; import com.sun.star.util.XChangesBatch; import com.sun.star.util.XCloseable; import com.sun.star.util.XModifiable; import com.sun.star.util.XURLTransformer; import java.io.IOException; import java.util.Iterator; import java.util.ArrayList; import java.util.logging.Level; import java.util.logging.Logger; // ---------- 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 static org.junit.Assert.*; // ------------------------------------------ public class DatabaseDocument extends TestCase implements com.sun.star.document.XDocumentEventListener { private static final String _BLANK = "_blank"; private XComponent m_callbackFactory = null; private final ArrayList m_documentEvents = new ArrayList(); private final ArrayList m_globalEvents = new ArrayList(); // for those states, see testDocumentEvents private static short STATE_NOT_STARTED = 0; private static short STATE_LOADING_DOC = 1; private static short STATE_MACRO_EXEC_APPROVED = 2; private static short STATE_ON_LOAD_RECEIVED = 3; private short m_loadDocState = STATE_NOT_STARTED; // ======================================================================================================== /** a helper class which can be used by the Basic scripts in our test documents * to notify us of events in this document */ private class CallbackComponent implements XDocumentEventListener, XTypeProvider { public void documentEventOccured(DocumentEvent _event) { onDocumentEvent(_event); } public void disposing(com.sun.star.lang.EventObject _Event) { // not interested in } public Type[] getTypes() { final Class interfaces[] = getClass().getInterfaces(); Type types[] = new Type[interfaces.length]; for (int i = 0; i < interfaces.length; ++i) { types[i] = new Type(interfaces[i]); } return types; } public byte[] getImplementationId() { return getClass().toString().getBytes(); } }; // ======================================================================================================== private static String getCallbackComponentServiceName() { return "org.openoffice.complex.dbaccess.EventCallback"; } // ======================================================================================================== /** a factory for a CallbackComponent */ private class CallbackComponentFactory implements XSingleComponentFactory, XServiceInfo, XComponent { private final ArrayList m_eventListeners = new ArrayList(); public Object createInstanceWithContext(XComponentContext _context) throws com.sun.star.uno.Exception { return new CallbackComponent(); } public Object createInstanceWithArgumentsAndContext(Object[] arg0, XComponentContext _context) throws com.sun.star.uno.Exception { return createInstanceWithContext(_context); } public String getImplementationName() { return "org.openoffice.complex.dbaccess.CallbackComponent"; } public boolean supportsService(String _service) { return _service.equals(getCallbackComponentServiceName()); } public String[] getSupportedServiceNames() { return new String[] { getCallbackComponentServiceName() }; } public void dispose() { final EventObject event = new EventObject(this); final ArrayList eventListenersCopy = (ArrayList) m_eventListeners.clone(); final Iterator iter = eventListenersCopy.iterator(); while (iter.hasNext()) { ((XEventListener) iter.next()).disposing(event); } } public void addEventListener(XEventListener _listener) { if (_listener != null) { m_eventListeners.add(_listener); } } public void removeEventListener(XEventListener _listener) { m_eventListeners.remove(_listener); } }; // ======================================================================================================== private class MacroExecutionApprove implements XInteractionHandler { private XInteractionHandler m_defaultHandler = null; MacroExecutionApprove(XMultiServiceFactory _factory) { try { m_defaultHandler = UnoRuntime.queryInterface(XInteractionHandler.class, _factory.createInstance("com.sun.star.task.InteractionHandler")); } catch (Exception ex) { Logger.getLogger(DatabaseDocument.class.getName()).log(Level.SEVERE, null, ex); } } public void handle(XInteractionRequest _request) { final Object request = _request.getRequest(); if (!(request instanceof DocumentMacroConfirmationRequest) && (m_defaultHandler != null)) { m_defaultHandler.handle(_request); return; } assertEquals("interaction handleer called in wrong state", STATE_LOADING_DOC, m_loadDocState); // auto-approve final XInteractionContinuation continuations[] = _request.getContinuations(); for (int i = 0; i < continuations.length; ++i) { final XInteractionApprove approve = UnoRuntime.queryInterface(XInteractionApprove.class, continuations[i]); if (approve != null) { approve.select(); m_loadDocState = STATE_MACRO_EXEC_APPROVED; break; } } } }; // ======================================================================================================== // -------------------------------------------------------------------------------------------------------- // public String[] getTestMethodNames() // { // return new String[] // { // "testLoadable", // "testDocumentRevenants", // "testDocumentEvents", // "testGlobalEvents" // }; // } // // // -------------------------------------------------------------------------------------------------------- // public String getTestObjectName() // { // return "DatabaseDocument"; // } // -------------------------------------------------------------------------------------------------------- @Before public void before() throws java.lang.Exception { super.before(); try { // at our service factory, insert a new factory for our CallbackComponent // this will allow the Basic code in our test documents to call back into this test case // here, by just instantiating this service final XSet globalFactory = UnoRuntime.queryInterface(XSet.class, getMSF()); m_callbackFactory = new CallbackComponentFactory(); globalFactory.insert(m_callbackFactory); // register ourself as listener at the global event broadcaster final XDocumentEventBroadcaster broadcaster = UnoRuntime.queryInterface(XDocumentEventBroadcaster.class, getMSF().createInstance("com.sun.star.frame.GlobalEventBroadcaster")); broadcaster.addDocumentEventListener(this); } catch (Exception e) { System.out.println("could not create the test case, error message:\n" + e.getMessage()); e.printStackTrace(System.err); fail("failed to create the test case"); } } // -------------------------------------------------------------------------------------------------------- @After public void after() throws java.lang.Exception { try { // dispose our callback factory. This will automatically remove it from our service // factory m_callbackFactory.dispose(); // revoke ourself as listener at the global event broadcaster final XDocumentEventBroadcaster broadcaster = UnoRuntime.queryInterface(XDocumentEventBroadcaster.class, getMSF().createInstance("com.sun.star.frame.GlobalEventBroadcaster")); broadcaster.removeDocumentEventListener(this); } catch (Exception e) { System.out.println("could not create the test case, error message:\n" + e.getMessage()); e.printStackTrace(System.err); fail("failed to close the test case"); } super.after(); } // -------------------------------------------------------------------------------------------------------- private static class UnoMethodDescriptor { public Class unoInterfaceClass = null; public String methodName = null; public UnoMethodDescriptor(Class _class, String _method) { unoInterfaceClass = _class; methodName = _method; } } // -------------------------------------------------------------------------------------------------------- private void impl_checkDocumentInitState(Object _document, boolean _isInitialized) { // things you cannot do with an uninitialized document: final UnoMethodDescriptor[] unsupportedMethods = new UnoMethodDescriptor[] { new UnoMethodDescriptor(XStorable.class, "store"), new UnoMethodDescriptor(XFormDocumentsSupplier.class, "getFormDocuments"), new UnoMethodDescriptor(XReportDocumentsSupplier.class, "getReportDocuments"), new UnoMethodDescriptor(XScriptProviderSupplier.class, "getScriptProvider"), new UnoMethodDescriptor(XEventsSupplier.class, "getEvents"), new UnoMethodDescriptor(XTitle.class, "getTitle"), new UnoMethodDescriptor(XModel2.class, "getControllers") // (there's much more than this, but we cannot list all methods here, can we ...) }; for (int i = 0; i < unsupportedMethods.length; ++i) { // assureException( _document, unsupportedMethods[i].unoInterfaceClass, // unsupportedMethods[i].methodName, new Object[]{}, _isInitialized ? null : NotInitializedException.class ); } } // -------------------------------------------------------------------------------------------------------- private XModel impl_createDocument() throws Exception { final XModel databaseDoc = UnoRuntime.queryInterface(XModel.class, getMSF().createInstance("com.sun.star.sdb.OfficeDatabaseDocument")); // should not be initialized here - we did neither initNew nor load nor storeAsURL it impl_checkDocumentInitState(databaseDoc, false); return databaseDoc; } // -------------------------------------------------------------------------------------------------------- private void impl_closeDocument(XModel _databaseDoc) throws CloseVetoException, IOException, Exception { final XCloseable closeDoc = UnoRuntime.queryInterface(XCloseable.class, _databaseDoc); closeDoc.close(true); } // -------------------------------------------------------------------------------------------------------- private XModel impl_createEmptyEmbeddedHSQLDocument() throws Exception, IOException { final XModel databaseDoc = UnoRuntime.queryInterface(XModel.class, getMSF().createInstance("com.sun.star.sdb.OfficeDatabaseDocument")); final XStorable storeDoc = UnoRuntime.queryInterface(XStorable.class, databaseDoc); // verify the document rejects API calls which require it to be initialized impl_checkDocumentInitState(databaseDoc, false); // though the document is not initialized, you can ask for the location, the URL, and the args final String location = storeDoc.getLocation(); final String url = databaseDoc.getURL(); final PropertyValue[] args = databaseDoc.getArgs(); // they should be all empty at this time assertEquals("location is expected to be empty here", "", location); assertEquals("URL is expected to be empty here", "", url); assertEquals("Args are expected to be empty here", 0, args.length); // and, you should be able to set properties at the data source final XOfficeDatabaseDocument dataSourceAccess = UnoRuntime.queryInterface(XOfficeDatabaseDocument.class, databaseDoc); final XPropertySet dsProperties = UnoRuntime.queryInterface(XPropertySet.class, dataSourceAccess.getDataSource()); dsProperties.setPropertyValue("URL", "sdbc:embedded:hsqldb"); final String documentURL = createTempFileURL(); storeDoc.storeAsURL(documentURL, new PropertyValue[0]); // now that the document is stored, ... // ... its URL should be correct assertEquals("wrong URL after storing the document", documentURL, databaseDoc.getURL()); // ... it should be initialized impl_checkDocumentInitState(databaseDoc, true); return databaseDoc; } // -------------------------------------------------------------------------------------------------------- @Test public void testLoadable() throws Exception, IOException { XModel databaseDoc = impl_createEmptyEmbeddedHSQLDocument(); String documentURL = databaseDoc.getURL(); // there's three methods how you can initialize a database document: // .................................................................... // 1. XStorable::storeAsURL // (this is for compatibility reasons, to not break existing code) // this test is already made in impl_createEmptyEmbeddedHSQLDocument // .................................................................... // 2. XLoadable::load databaseDoc = UnoRuntime.queryInterface(XModel.class, getMSF().createInstance("com.sun.star.sdb.OfficeDatabaseDocument")); documentURL = copyToTempFile(documentURL); // load the doc, and verify it's initialized then, and has the proper URL XLoadable loadDoc = UnoRuntime.queryInterface(XLoadable.class, databaseDoc); loadDoc.load(new PropertyValue[] { new PropertyValue("URL", 0, documentURL, PropertyState.DIRECT_VALUE) }); databaseDoc.attachResource(documentURL, new PropertyValue[0]); assertEquals("wrong URL after loading the document", documentURL, databaseDoc.getURL()); impl_checkDocumentInitState(databaseDoc, true); // and while we are here ... initilizing the same document again should not be possible // assureException( databaseDoc, XLoadable.class, "initNew", new Object[0], // DoubleInitializationException.class ); // assureException( databaseDoc, XLoadable.class, "load", new Object[] { new PropertyValue[0] }, // DoubleInitializationException.class ); // .................................................................... // 3. XLoadable::initNew impl_closeDocument(databaseDoc); databaseDoc = impl_createDocument(); loadDoc = UnoRuntime.queryInterface(XLoadable.class, databaseDoc); loadDoc.initNew(); assertEquals("wrong URL after initializing the document", "", databaseDoc.getURL()); impl_checkDocumentInitState(databaseDoc, true); // same as above - initializing the document a second time must fail // assureException( databaseDoc, XLoadable.class, "initNew", new Object[0], // DoubleInitializationException.class ); // assureException( databaseDoc, XLoadable.class, "load", new Object[] { new PropertyValue[0] }, // DoubleInitializationException.class ); } // -------------------------------------------------------------------------------------------------------- private PropertyValue[] impl_getMarkerLoadArgs() { return new PropertyValue[] { new PropertyValue( "PickListEntry", 0, false, PropertyState.DIRECT_VALUE ), new PropertyValue( "TestCase_Marker", 0, "Yes", PropertyState.DIRECT_VALUE ) }; } // -------------------------------------------------------------------------------------------------------- private boolean impl_hasMarker( final PropertyValue[] _args ) { for ( int i=0; i<_args.length; ++i ) { if ( _args[i].Name.equals( "TestCase_Marker" ) && _args[i].Value.equals( "Yes" ) ) { return true; } } return false; } // -------------------------------------------------------------------------------------------------------- private PropertyValue[] impl_getDefaultLoadArgs() { return new PropertyValue[] { new PropertyValue("PickListEntry", 0, false, PropertyState.DIRECT_VALUE) }; } // -------------------------------------------------------------------------------------------------------- private PropertyValue[] impl_getMacroExecLoadArgs() { return new PropertyValue[] { new PropertyValue("PickListEntry", 0, false, PropertyState.DIRECT_VALUE), new PropertyValue("MacroExecutionMode", 0, com.sun.star.document.MacroExecMode.USE_CONFIG, PropertyState.DIRECT_VALUE), new PropertyValue("InteractionHandler", 0, new MacroExecutionApprove(getMSF()), PropertyState.DIRECT_VALUE) }; } // -------------------------------------------------------------------------------------------------------- private int impl_setMacroSecurityLevel(int _level) throws Exception { final XMultiServiceFactory configProvider = UnoRuntime.queryInterface(XMultiServiceFactory.class, getMSF().createInstance("com.sun.star.configuration.ConfigurationProvider")); final PropertyValue[] args = new PropertyValue[] { new PropertyValue("nodepath", 0, "/org.openoffice.Office.Common/Security/Scripting", PropertyState.DIRECT_VALUE) }; final XPropertySet securitySettings = UnoRuntime.queryInterface(XPropertySet.class, configProvider.createInstanceWithArguments("com.sun.star.configuration.ConfigurationUpdateAccess", args)); final int oldValue = ((Integer) securitySettings.getPropertyValue("MacroSecurityLevel")).intValue(); securitySettings.setPropertyValue("MacroSecurityLevel", Integer.valueOf(_level)); final XChangesBatch committer = UnoRuntime.queryInterface(XChangesBatch.class, securitySettings); committer.commitChanges(); return oldValue; } // -------------------------------------------------------------------------------------------------------- private XModel impl_loadDocument( final String _documentURL, final PropertyValue[] _loadArgs ) throws Exception { final XComponentLoader loader = UnoRuntime.queryInterface(XComponentLoader.class, getMSF().createInstance("com.sun.star.frame.Desktop")); return UnoRuntime.queryInterface(XModel.class, loader.loadComponentFromURL(_documentURL, _BLANK, 0, _loadArgs)); } // -------------------------------------------------------------------------------------------------------- private void impl_storeDocument( final XModel _document ) throws Exception, IOException { // store the document final String documentURL = FileHelper.getOOoCompatibleFileURL( _document.getURL() ); final XStorable storeDoc = UnoRuntime.queryInterface(XStorable.class, _document); storeDoc.store(); } // -------------------------------------------------------------------------------------------------------- private XModel impl_createDocWithMacro( final String _libName, final String _moduleName, final String _code ) throws Exception, IOException { // create an empty document XModel databaseDoc = impl_createEmptyEmbeddedHSQLDocument(); // create Basic library/module therein final XEmbeddedScripts embeddedScripts = UnoRuntime.queryInterface(XEmbeddedScripts.class, databaseDoc); final XStorageBasedLibraryContainer basicLibs = embeddedScripts.getBasicLibraries(); final XNameContainer newLib = basicLibs.createLibrary( _libName ); newLib.insertByName( _moduleName, _code ); return databaseDoc; } // -------------------------------------------------------------------------------------------------------- /** tests various aspects of database document "revenants" * * Well, I do not really have a good term for this ... The point is, database documents are in real * only *one* aspect of a more complex thing. The second aspect is a data source. Both, in some sense, * just represent different views on the same thing. For a given database, there's at each time at most * one data source, and at most one database document. Both have a independent life time, and are * created when needed. * In particular, a document can be closed (this is what happens when the last UI window displaying * this document is closed), and then dies. Now when the other "view", the data source, still exists, * the the underlying document data is not discarded, but kept alive (else the data source would die * just because the document dies, which is not desired). If the document is loaded, again, then * it is re-created, using the data of its previous "incarnation". * * This method here tests some of those aspects of a document which should survive the death of one * instance and re-creation as a revenant. */ @Test public void testDocumentRevenants() throws Exception, IOException { // create an empty document XModel databaseDoc = impl_createDocWithMacro( "Lib", "Module", "Sub Hello\n" + " MsgBox \"Hello\"\n" + "End Sub\n" ); impl_storeDocument( databaseDoc ); final String documentURL = databaseDoc.getURL(); // at this stage, the marker should not yet be present in the doc's args, else some of the below // tests become meaningless assertTrue( "A newly created doc should not have the test case marker", !impl_hasMarker( databaseDoc.getArgs() ) ); // obtain the DataSource associated with the document. Keeping this alive // ensures that the "impl data" of the document is kept alive, too, so when closing // and re-opening it, this "impl data" must be re-used. XDocumentDataSource dataSource = UnoRuntime.queryInterface(XDocumentDataSource.class, (UnoRuntime.queryInterface(XOfficeDatabaseDocument.class, databaseDoc)).getDataSource()); // close and reload the doc impl_closeDocument(databaseDoc); databaseDoc = impl_loadDocument( documentURL, impl_getMarkerLoadArgs() ); // since we just put the marker into the load-call, it should be present at the doc assertTrue( "The test case marker got lost.", impl_hasMarker( databaseDoc.getArgs() ) ); // The basic library should have survived final XEmbeddedScripts embeddedScripts = UnoRuntime.queryInterface(XEmbeddedScripts.class, databaseDoc); final XStorageBasedLibraryContainer basicLibs = embeddedScripts.getBasicLibraries(); assertTrue( "Baisc lib did not survive reloading a closed document", basicLibs.hasByName( "Lib" ) ); final XNameContainer lib = UnoRuntime.queryInterface(XNameContainer.class, basicLibs.getByName("Lib")); assertTrue( "Basic module did not survive reloading a closed document", lib.hasByName( "Module" ) ); // now closing the doc, and obtaining it from the data source, should preserve the marker we put into the load // args impl_closeDocument( databaseDoc ); databaseDoc = UnoRuntime.queryInterface(XModel.class, dataSource.getDatabaseDocument()); assertTrue( "The test case marker did not survive re-retrieval of the doc from the data source.", impl_hasMarker( databaseDoc.getArgs() ) ); // on the other hand, closing and regurlarly re-loading the doc *without* the marker should indeed // lose it impl_closeDocument( databaseDoc ); databaseDoc = impl_loadDocument( documentURL, impl_getDefaultLoadArgs() ); assertTrue( "Reloading the document kept the old args, instead of the newly supplied ones.", !impl_hasMarker( databaseDoc.getArgs() ) ); // clean up impl_closeDocument( databaseDoc ); } // -------------------------------------------------------------------------------------------------------- @Test public void testDocumentEvents() throws Exception, IOException { // create an empty document final String libName = "EventHandlers"; final String moduleName = "all"; final String eventHandlerCode = "Option Explicit\n" + "\n" + "Sub OnLoad\n" + " Dim oCallback as Object\n" + " oCallback = createUnoService( \"" + getCallbackComponentServiceName() + "\" )\n" + "\n" + " ' as long as the Document is not passed to the Basic callbacks, we need to create\n" + " ' one ourself\n" + " Dim oEvent as new com.sun.star.document.DocumentEvent\n" + " oEvent.EventName = \"OnLoad\"\n" + " oEvent.Source = ThisComponent\n" + "\n" + " oCallback.documentEventOccured( oEvent )\n" + "End Sub\n"; XModel databaseDoc = impl_createDocWithMacro( libName, moduleName, eventHandlerCode ); final String documentURL = databaseDoc.getURL(); // bind the macro to the OnLoad event final String macroURI = "vnd.sun.star.script:" + libName + "." + moduleName + ".OnLoad?language=Basic&location=document"; final XEventsSupplier eventsSupplier = UnoRuntime.queryInterface(XEventsSupplier.class, databaseDoc); eventsSupplier.getEvents().replaceByName("OnLoad", new PropertyValue[] { new PropertyValue("EventType", 0, "Script", PropertyState.DIRECT_VALUE), new PropertyValue("Script", 0, macroURI, PropertyState.DIRECT_VALUE) }); // store the document, and close it impl_storeDocument( databaseDoc ); impl_closeDocument( databaseDoc ); // ensure the macro security configuration is "ask the user for document macro execution" final int oldSecurityLevel = impl_setMacroSecurityLevel(1); // load it, again m_loadDocState = STATE_LOADING_DOC; // expected order of states is: // STATE_LOADING_DOC - initialized here // STATE_MACRO_EXEC_APPROVED - done in our interaction handler, which auto-approves the execution of macros // STATE_ON_LOAD_RECEIVED - done in our callback for the document events // // In particular, it is important that the interaction handler (which plays the role of the user confirmation // here) is called before the OnLoad notification is received - since the latter happens from within // a Basic macro which is bound to the OnLoad event of the document. final String context = "OnLoad"; impl_startObservingEvents(context); databaseDoc = impl_loadDocument( documentURL, impl_getMacroExecLoadArgs() ); impl_stopObservingEvents(m_documentEvents, new String[] { "OnLoad" }, context); assertEquals("our provided interaction handler was not called", STATE_ON_LOAD_RECEIVED, m_loadDocState); // restore macro security level impl_setMacroSecurityLevel(oldSecurityLevel); // close the document impl_closeDocument(databaseDoc); } // -------------------------------------------------------------------------------------------------------- @Test public void testGlobalEvents() throws Exception, IOException { XModel databaseDoc = impl_createEmptyEmbeddedHSQLDocument(); final XStorable storeDoc = UnoRuntime.queryInterface(XStorable.class, databaseDoc); String context, newURL; // XStorable.store final String oldURL = databaseDoc.getURL(); context = "store"; impl_startObservingEvents(context); storeDoc.store(); assertEquals("store is not expected to change the document URL", databaseDoc.getURL(), oldURL); impl_stopObservingEvents(m_globalEvents, new String[] { "OnSave", "OnSaveDone" }, context); // XStorable.storeToURL context = "storeToURL"; impl_startObservingEvents(context); storeDoc.storeToURL(createTempFileURL(), new PropertyValue[0]); assertEquals("storetoURL is not expected to change the document URL", databaseDoc.getURL(), oldURL); impl_stopObservingEvents(m_globalEvents, new String[] { "OnSaveTo", "OnSaveToDone" }, context); // XStorable.storeAsURL newURL = createTempFileURL(); context = "storeAsURL"; impl_startObservingEvents(context); storeDoc.storeAsURL(newURL, new PropertyValue[0]); assertEquals("storeAsURL is expected to change the document URL", databaseDoc.getURL(), newURL); impl_stopObservingEvents(m_globalEvents, new String[] { "OnSaveAs", "OnSaveAsDone" }, context); // XModifiable.setModified final XModifiable modifyDoc = UnoRuntime.queryInterface(XModifiable.class, databaseDoc); context = "setModified"; impl_startObservingEvents(context); modifyDoc.setModified(true); assertEquals("setModified didn't work", modifyDoc.isModified(), true); impl_stopObservingEvents(m_globalEvents, new String[] { "OnModifyChanged" }, context); // XStorable.store, with implicit reset of the "Modified" flag context = "store (2)"; impl_startObservingEvents(context); storeDoc.store(); assertEquals("'store' should implicitly reset the modified flag", modifyDoc.isModified(), false); impl_stopObservingEvents(m_globalEvents, new String[] { "OnSave", "OnSaveDone", "OnModifyChanged" }, context); // XComponentLoader.loadComponentFromURL newURL = copyToTempFile(databaseDoc.getURL()); final XComponentLoader loader = UnoRuntime.queryInterface(XComponentLoader.class, getMSF().createInstance("com.sun.star.frame.Desktop")); context = "loadComponentFromURL"; impl_startObservingEvents(context); databaseDoc = UnoRuntime.queryInterface(XModel.class, loader.loadComponentFromURL(newURL, _BLANK, 0, impl_getDefaultLoadArgs())); impl_stopObservingEvents(m_globalEvents, new String[] { "OnLoadFinished", "OnViewCreated", "OnFocus", "OnLoad" }, context); // closing a document by API final XCloseable closeDoc = UnoRuntime.queryInterface(XCloseable.class, databaseDoc); context = "close (API)"; impl_startObservingEvents(context); closeDoc.close(true); impl_stopObservingEvents(m_globalEvents, new String[] { "OnPrepareUnload", "OnViewClosed", "OnUnload" }, context); // closing a document via UI context = "close (UI)"; impl_startObservingEvents("prepare for '" + context + "'"); databaseDoc = UnoRuntime.queryInterface(XModel.class, loader.loadComponentFromURL(newURL, _BLANK, 0, impl_getDefaultLoadArgs())); impl_waitForEvent(m_globalEvents, "OnLoad", 5000); // wait for all events to arrive - OnLoad should be the last one final XDispatchProvider dispatchProvider = UnoRuntime.queryInterface(XDispatchProvider.class, databaseDoc.getCurrentController().getFrame()); final URL url = impl_getURL(".uno:CloseDoc"); final XDispatch dispatcher = dispatchProvider.queryDispatch(url, "", 0); impl_startObservingEvents(context); dispatcher.dispatch(url, new PropertyValue[0]); impl_stopObservingEvents(m_globalEvents, new String[] { "OnPrepareViewClosing", "OnViewClosed", "OnPrepareUnload", "OnUnload" }, context); // creating a new document databaseDoc = impl_createDocument(); final XLoadable loadDoc = UnoRuntime.queryInterface(XLoadable.class, databaseDoc); context = "initNew"; impl_startObservingEvents(context); loadDoc.initNew(); impl_stopObservingEvents(m_globalEvents, new String[] { "OnCreate" }, context); impl_startObservingEvents(context + " (cleanup)"); impl_closeDocument(databaseDoc); impl_waitForEvent(m_globalEvents, "OnUnload", 5000); // focus changes context = "activation"; // for this, load a database document ... impl_startObservingEvents("prepare for '" + context + "'"); databaseDoc = UnoRuntime.queryInterface(XModel.class, loader.loadComponentFromURL(newURL, _BLANK, 0, impl_getDefaultLoadArgs())); final int previousOnLoadEventPos = impl_waitForEvent(m_globalEvents, "OnLoad", 5000); // ... and another document ... final String otherURL = copyToTempFile(databaseDoc.getURL()); final XModel otherDoc = UnoRuntime.queryInterface(XModel.class, loader.loadComponentFromURL(otherURL, _BLANK, 0, impl_getDefaultLoadArgs())); impl_raise(otherDoc); impl_waitForEvent(m_globalEvents, "OnLoad", 5000, previousOnLoadEventPos + 1); // ... and switch between the two impl_startObservingEvents(context); impl_raise(databaseDoc); impl_stopObservingEvents(m_globalEvents, new String[] { "OnUnfocus", "OnFocus" }, context); // cleanup impl_startObservingEvents("cleanup after '" + context + "'"); impl_closeDocument(databaseDoc); impl_closeDocument(otherDoc); } // -------------------------------------------------------------------------------------------------------- private URL impl_getURL(String _completeURL) throws Exception { final URL[] url = { new URL() }; url[0].Complete = _completeURL; final XURLTransformer urlTransformer = UnoRuntime.queryInterface(XURLTransformer.class, getMSF().createInstance("com.sun.star.util.URLTransformer")); urlTransformer.parseStrict(url); return url[0]; } // -------------------------------------------------------------------------------------------------------- private void impl_raise(XModel _document) { final XFrame frame = _document.getCurrentController().getFrame(); final XTopWindow topWindow = UnoRuntime.queryInterface(XTopWindow.class, frame.getContainerWindow()); topWindow.toFront(); } // -------------------------------------------------------------------------------------------------------- private void impl_startObservingEvents(String _context) { System.out.println(" " + _context + " {"); synchronized (m_documentEvents) { m_documentEvents.clear(); } synchronized (m_globalEvents) { m_globalEvents.clear(); } } // -------------------------------------------------------------------------------------------------------- private void impl_stopObservingEvents(ArrayList _actualEvents, String[] _expectedEvents, String _context) { try { synchronized (_actualEvents) { int actualEventCount = _actualEvents.size(); while (actualEventCount < _expectedEvents.length) { // well, it's possible not all events already arrived, yet - finally, some of them // are notified asynchronously // So, wait a few seconds. try { _actualEvents.wait(20000); } catch (InterruptedException ex) { } if (actualEventCount == _actualEvents.size()) // the above wait was left because of the timeout, *not* because an event // arrived. Okay, we won't wait any longer, this is a failure. { break; } actualEventCount = _actualEvents.size(); } assertEquals("wrong event count for '" + _context + "'", _expectedEvents.length, _actualEvents.size()); for (int i = 0; i < _expectedEvents.length; ++i) { assertEquals("wrong event at positon " + (i + 1) + " for '" + _context + "'", _expectedEvents[i], _actualEvents.get(i)); } } } finally { System.out.println(" }"); } } // -------------------------------------------------------------------------------------------------------- int impl_waitForEvent(ArrayList _eventQueue, String _expectedEvent, int _maxMilliseconds) { return impl_waitForEvent(_eventQueue, _expectedEvent, _maxMilliseconds, 0); } // -------------------------------------------------------------------------------------------------------- int impl_waitForEvent(ArrayList _eventQueue, String _expectedEvent, int _maxMilliseconds, int _firstQueueElementToCheck) { synchronized (_eventQueue) { int waitedMilliseconds = 0; while (waitedMilliseconds < _maxMilliseconds) { for (int i = _firstQueueElementToCheck; i < _eventQueue.size(); ++i) { if (_expectedEvent.equals(_eventQueue.get(i))) // found the event in the queue { return i; } } // wait a little, perhaps the event will still arrive try { _eventQueue.wait(500); waitedMilliseconds += 500; } catch (InterruptedException e) { } } } fail("expected event '" + _expectedEvent + "' did not arrive after " + _maxMilliseconds + " milliseconds"); return -1; } // -------------------------------------------------------------------------------------------------------- void onDocumentEvent(DocumentEvent _Event) { if ("OnTitleChanged".equals(_Event.EventName)) // OnTitleChanged events are notified too often. This is known, and accepted. // (the deeper reason is that it's diffult to determine, in the DatabaseDocument implementatin, // when the title actually changed. In particular, when we do a saveAsURL, and then ask for a // title *before* the TitleHelper got the document's OnSaveAsDone event, then the wrong (old) // title is obtained. { return; } if ((_Event.EventName.equals("OnLoad")) && (m_loadDocState != STATE_NOT_STARTED)) { assertEquals("OnLoad event must come *after* invocation of the interaction handler / user!", m_loadDocState, STATE_MACRO_EXEC_APPROVED); m_loadDocState = STATE_ON_LOAD_RECEIVED; } synchronized (m_documentEvents) { m_documentEvents.add(_Event.EventName); m_documentEvents.notifyAll(); } System.out.println(" document event: " + _Event.EventName); } // -------------------------------------------------------------------------------------------------------- public void documentEventOccured(DocumentEvent _Event) { if ("OnTitleChanged".equals(_Event.EventName)) // ignore. See onDocumentEvent for a justification { return; } synchronized (m_globalEvents) { m_globalEvents.add(_Event.EventName); m_globalEvents.notifyAll(); } System.out.println(" global event: " + _Event.EventName); } // -------------------------------------------------------------------------------------------------------- public void disposing(EventObject _Event) { // not interested in } }