and annotate where necessary, mostly just suppressing the warnings Change-Id: I8e39d797cde6c7c3f4e3e1bd93a128965ecec81d Reviewed-on: https://gerrit.libreoffice.org/c/core/+/159205 Tested-by: Jenkins Reviewed-by: Noel Grandin <noel.grandin@collabora.co.uk>
997 lines
39 KiB
Java
997 lines
39 KiB
Java
/*
|
|
* 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()
|
|
};
|
|
}
|
|
|
|
@SuppressWarnings("unchecked")
|
|
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
|
|
}
|
|
}
|