summaryrefslogtreecommitdiffstats
path: root/dbaccess/qa/complex/dbaccess/DatabaseDocument.java
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 09:06:44 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 09:06:44 +0000
commited5640d8b587fbcfed7dd7967f3de04b37a76f26 (patch)
tree7a5f7c6c9d02226d7471cb3cc8fbbf631b415303 /dbaccess/qa/complex/dbaccess/DatabaseDocument.java
parentInitial commit. (diff)
downloadlibreoffice-ed5640d8b587fbcfed7dd7967f3de04b37a76f26.tar.xz
libreoffice-ed5640d8b587fbcfed7dd7967f3de04b37a76f26.zip
Adding upstream version 4:7.4.7.upstream/4%7.4.7upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'dbaccess/qa/complex/dbaccess/DatabaseDocument.java')
-rw-r--r--dbaccess/qa/complex/dbaccess/DatabaseDocument.java995
1 files changed, 995 insertions, 0 deletions
diff --git a/dbaccess/qa/complex/dbaccess/DatabaseDocument.java b/dbaccess/qa/complex/dbaccess/DatabaseDocument.java
new file mode 100644
index 000000000..5bd28053b
--- /dev/null
+++ b/dbaccess/qa/complex/dbaccess/DatabaseDocument.java
@@ -0,0 +1,995 @@
+/*
+ * 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.dbaccess;
+
+import com.sun.star.configuration.theDefaultProvider;
+import com.sun.star.lang.NotInitializedException;
+import com.sun.star.frame.DoubleInitializationException;
+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.NamedValue;
+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.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.XGlobalEventBroadcaster;
+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.frame.theGlobalEventBroadcaster;
+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.Before;
+import org.junit.Test;
+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<String> m_documentEvents = new ArrayList<String>();
+ private final ArrayList<String> m_globalEvents = new ArrayList<String>();
+ // 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 new byte[0];
+ }
+ }
+
+
+ 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<XEventListener> m_eventListeners = new ArrayList<XEventListener>();
+
+ 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<XEventListener> eventListenersCopy = (ArrayList<XEventListener>)m_eventListeners.clone();
+ final Iterator<XEventListener> iter = eventListenersCopy.iterator();
+ while (iter.hasNext())
+ {
+ 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 handler 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;
+ }
+ }
+ }
+ }
+
+
+
+ @Override
+ @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 XGlobalEventBroadcaster broadcaster
+ = theGlobalEventBroadcaster.get(getComponentContext());
+ 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");
+ }
+ }
+
+
+ @Override
+ @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 XGlobalEventBroadcaster broadcaster
+ = theGlobalEventBroadcaster.get(getComponentContext());
+ 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;
+
+ 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, 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 ... initializing 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 = theDefaultProvider.get(
+ getComponentContext());
+
+ final NamedValue[] args = new NamedValue[]
+ {
+ new NamedValue("nodepath", "/org.openoffice.Office.Common/Security/Scripting")
+ };
+
+ 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
+ {
+ // store the document
+ 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 an 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 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( "Basic 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 regularly 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.documentEventOccurred( 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<String> _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 position " + (i + 1) + " for '" + _context + "'",
+ _expectedEvents[i], _actualEvents.get(i));
+ }
+ }
+ }
+ finally
+ {
+ System.out.println(" }");
+ }
+ }
+
+
+ private int impl_waitForEvent(ArrayList<String> _eventQueue, String _expectedEvent, int _maxMilliseconds)
+ {
+ return impl_waitForEvent(_eventQueue, _expectedEvent, _maxMilliseconds, 0);
+ }
+
+
+ private int impl_waitForEvent(ArrayList<String> _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;
+ }
+
+
+ private 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 difficult to determine, in the DatabaseDocument implementation,
+ // 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
+ }
+}