/* * 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;
privatestaticfinal String _BLANK = "_blank"; private XComponent m_callbackFactory = null; privatefinal ArrayList<String> m_documentEvents = new ArrayList<String>(); privatefinal ArrayList<String> m_globalEvents = new ArrayList<String>(); // for those states, see testDocumentEvents privatestaticshort STATE_NOT_STARTED = 0; privatestaticshort STATE_LOADING_DOC = 1; privatestaticshort STATE_MACRO_EXEC_APPROVED = 2; privatestaticshort STATE_ON_LOAD_RECEIVED = 3; privateshort 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
*/ privateclass CallbackComponent implements XDocumentEventListener, XTypeProvider
{
publicvoid disposing(com.sun.star.lang.EventObject _Event)
{ // not interested in
}
public Type[] getTypes()
{ finalClass 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 String[] getSupportedServiceNames()
{ returnnew String[]
{
getCallbackComponentServiceName()
};
}
@SuppressWarnings("unchecked") publicvoid 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);
}
}
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 publicvoid 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();
}
privatestaticclass UnoMethodDescriptor
{
publicClass unoInterfaceClass = null; public String methodName = null;
privatevoid 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_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);
// 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 );
// 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 );
}
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));
}
privatevoid 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 publicvoid 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 publicvoid 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" finalint 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);
// 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);
// 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);
// 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);
// 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())); finalint 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);
privatevoid 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();
}
privateint 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;
}
privatevoid 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;
}
publicvoid documentEventOccured(DocumentEvent _Event)
{ if ("OnTitleChanged".equals(_Event.EventName)) // ignore. See onDocumentEvent for a justification
{ return;
}
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.