1 /************************************************************************* 2 * 3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 4 * 5 * Copyright 2000, 2010 Oracle and/or its affiliates. 6 * 7 * OpenOffice.org - a multi-platform office productivity suite 8 * 9 * This file is part of OpenOffice.org. 10 * 11 * OpenOffice.org is free software: you can redistribute it and/or modify 12 * it under the terms of the GNU Lesser General Public License version 3 13 * only, as published by the Free Software Foundation. 14 * 15 * OpenOffice.org is distributed in the hope that it will be useful, 16 * but WITHOUT ANY WARRANTY; without even the implied warranty of 17 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 18 * GNU Lesser General Public License version 3 for more details 19 * (a copy is included in the LICENSE file that accompanied this code). 20 * 21 * You should have received a copy of the GNU Lesser General Public License 22 * version 3 along with OpenOffice.org. If not, see 23 * <http://www.openoffice.org/license.html> 24 * for a copy of the LGPLv3 License. 25 * 26 ************************************************************************/ 27 package complex.dbaccess; 28 29 import com.sun.star.lang.NotInitializedException; 30 import com.sun.star.frame.DoubleInitializationException; 31 import com.sun.star.awt.XTopWindow; 32 import com.sun.star.beans.PropertyState; 33 import com.sun.star.document.DocumentEvent; 34 import com.sun.star.lang.XEventListener; 35 import com.sun.star.lang.XMultiServiceFactory; 36 import com.sun.star.script.XStorageBasedLibraryContainer; 37 import com.sun.star.task.XInteractionRequest; 38 39 import com.sun.star.uno.Type; 40 import com.sun.star.uno.UnoRuntime; 41 import com.sun.star.frame.XStorable; 42 import com.sun.star.beans.PropertyValue; 43 import com.sun.star.beans.XPropertySet; 44 import com.sun.star.container.XNameContainer; 45 import com.sun.star.container.XSet; 46 import com.sun.star.document.XDocumentEventBroadcaster; 47 import com.sun.star.document.XDocumentEventListener; 48 import com.sun.star.document.XEmbeddedScripts; 49 import com.sun.star.document.XEventsSupplier; 50 import com.sun.star.lang.XComponent; 51 import com.sun.star.frame.XComponentLoader; 52 import com.sun.star.frame.XDispatch; 53 import com.sun.star.frame.XDispatchProvider; 54 import com.sun.star.frame.XFrame; 55 import com.sun.star.frame.XLoadable; 56 import com.sun.star.frame.XModel; 57 import com.sun.star.frame.XModel2; 58 import com.sun.star.frame.XTitle; 59 import com.sun.star.lang.EventObject; 60 import com.sun.star.lang.XServiceInfo; 61 import com.sun.star.lang.XSingleComponentFactory; 62 import com.sun.star.lang.XTypeProvider; 63 import com.sun.star.script.provider.XScriptProviderSupplier; 64 import com.sun.star.sdb.XDocumentDataSource; 65 66 import com.sun.star.sdb.XFormDocumentsSupplier; 67 import com.sun.star.sdb.XOfficeDatabaseDocument; 68 import com.sun.star.sdb.XReportDocumentsSupplier; 69 import com.sun.star.task.DocumentMacroConfirmationRequest; 70 import com.sun.star.task.XInteractionApprove; 71 import com.sun.star.task.XInteractionContinuation; 72 import com.sun.star.task.XInteractionHandler; 73 import com.sun.star.uno.XComponentContext; 74 import com.sun.star.util.CloseVetoException; 75 import com.sun.star.util.URL; 76 import com.sun.star.util.XChangesBatch; 77 import com.sun.star.util.XCloseable; 78 import com.sun.star.util.XModifiable; 79 import com.sun.star.util.XURLTransformer; 80 import java.io.IOException; 81 import java.util.Iterator; 82 import java.util.ArrayList; 83 import java.util.logging.Level; 84 import java.util.logging.Logger; 85 86 // ---------- junit imports ----------------- 87 import org.junit.After; 88 import org.junit.Before; 89 import org.junit.Test; 90 import static org.junit.Assert.*; 91 // ------------------------------------------ 92 93 public class DatabaseDocument extends TestCase implements com.sun.star.document.XDocumentEventListener 94 { 95 96 private static final String _BLANK = "_blank"; 97 private XComponent m_callbackFactory = null; 98 private final ArrayList m_documentEvents = new ArrayList(); 99 private final ArrayList m_globalEvents = new ArrayList(); 100 // for those states, see testDocumentEvents 101 private static short STATE_NOT_STARTED = 0; 102 private static short STATE_LOADING_DOC = 1; 103 private static short STATE_MACRO_EXEC_APPROVED = 2; 104 private static short STATE_ON_LOAD_RECEIVED = 3; 105 private short m_loadDocState = STATE_NOT_STARTED; 106 107 // ======================================================================================================== 108 /** a helper class which can be used by the Basic scripts in our test documents 109 * to notify us of events in this document 110 */ 111 private class CallbackComponent implements XDocumentEventListener, XTypeProvider 112 { 113 114 public void documentEventOccured(DocumentEvent _event) 115 { 116 onDocumentEvent(_event); 117 } 118 119 public void disposing(com.sun.star.lang.EventObject _Event) 120 { 121 // not interested in 122 } 123 124 public Type[] getTypes() 125 { 126 final Class interfaces[] = getClass().getInterfaces(); 127 Type types[] = new Type[interfaces.length]; 128 for (int i = 0; i < interfaces.length; ++i) 129 { 130 types[i] = new Type(interfaces[i]); 131 } 132 return types; 133 } 134 135 public byte[] getImplementationId() 136 { 137 return getClass().toString().getBytes(); 138 } 139 }; 140 141 // ======================================================================================================== 142 private static String getCallbackComponentServiceName() 143 { 144 return "org.openoffice.complex.dbaccess.EventCallback"; 145 } 146 147 // ======================================================================================================== 148 /** a factory for a CallbackComponent 149 */ 150 private class CallbackComponentFactory implements XSingleComponentFactory, XServiceInfo, XComponent 151 { 152 153 private final ArrayList m_eventListeners = new ArrayList(); 154 155 public Object createInstanceWithContext(XComponentContext _context) throws com.sun.star.uno.Exception 156 { 157 return new CallbackComponent(); 158 } 159 160 public Object createInstanceWithArgumentsAndContext(Object[] arg0, XComponentContext _context) throws com.sun.star.uno.Exception 161 { 162 return createInstanceWithContext(_context); 163 } 164 165 public String getImplementationName() 166 { 167 return "org.openoffice.complex.dbaccess.CallbackComponent"; 168 } 169 170 public boolean supportsService(String _service) 171 { 172 return _service.equals(getCallbackComponentServiceName()); 173 } 174 175 public String[] getSupportedServiceNames() 176 { 177 return new String[] 178 { 179 getCallbackComponentServiceName() 180 }; 181 } 182 183 public void dispose() 184 { 185 final EventObject event = new EventObject(this); 186 187 final ArrayList eventListenersCopy = (ArrayList) m_eventListeners.clone(); 188 final Iterator iter = eventListenersCopy.iterator(); 189 while (iter.hasNext()) 190 { 191 ((XEventListener) iter.next()).disposing(event); 192 } 193 } 194 195 public void addEventListener(XEventListener _listener) 196 { 197 if (_listener != null) 198 { 199 m_eventListeners.add(_listener); 200 } 201 } 202 203 public void removeEventListener(XEventListener _listener) 204 { 205 m_eventListeners.remove(_listener); 206 } 207 }; 208 209 // ======================================================================================================== 210 private class MacroExecutionApprove implements XInteractionHandler 211 { 212 213 private XInteractionHandler m_defaultHandler = null; 214 215 MacroExecutionApprove(XMultiServiceFactory _factory) 216 { 217 try 218 { 219 m_defaultHandler = UnoRuntime.queryInterface(XInteractionHandler.class, _factory.createInstance("com.sun.star.task.InteractionHandler")); 220 } 221 catch (Exception ex) 222 { 223 Logger.getLogger(DatabaseDocument.class.getName()).log(Level.SEVERE, null, ex); 224 } 225 } 226 227 public void handle(XInteractionRequest _request) 228 { 229 final Object request = _request.getRequest(); 230 if (!(request instanceof DocumentMacroConfirmationRequest) && (m_defaultHandler != null)) 231 { 232 m_defaultHandler.handle(_request); 233 return; 234 } 235 236 assertEquals("interaction handleer called in wrong state", STATE_LOADING_DOC, m_loadDocState); 237 238 // auto-approve 239 final XInteractionContinuation continuations[] = _request.getContinuations(); 240 for (int i = 0; i < continuations.length; ++i) 241 { 242 final XInteractionApprove approve = UnoRuntime.queryInterface(XInteractionApprove.class, continuations[i]); 243 if (approve != null) 244 { 245 approve.select(); 246 m_loadDocState = STATE_MACRO_EXEC_APPROVED; 247 break; 248 } 249 } 250 } 251 }; 252 253 // ======================================================================================================== 254 // -------------------------------------------------------------------------------------------------------- 255 @Before 256 public void before() throws java.lang.Exception 257 { 258 super.before(); 259 260 try 261 { 262 // at our service factory, insert a new factory for our CallbackComponent 263 // this will allow the Basic code in our test documents to call back into this test case 264 // here, by just instantiating this service 265 final XSet globalFactory = UnoRuntime.queryInterface(XSet.class, getMSF()); 266 m_callbackFactory = new CallbackComponentFactory(); 267 globalFactory.insert(m_callbackFactory); 268 269 // register ourself as listener at the global event broadcaster 270 final XDocumentEventBroadcaster broadcaster = UnoRuntime.queryInterface(XDocumentEventBroadcaster.class, getMSF().createInstance("com.sun.star.frame.GlobalEventBroadcaster")); 271 broadcaster.addDocumentEventListener(this); 272 } 273 catch (Exception e) 274 { 275 System.out.println("could not create the test case, error message:\n" + e.getMessage()); 276 e.printStackTrace(System.err); 277 fail("failed to create the test case"); 278 } 279 } 280 281 // -------------------------------------------------------------------------------------------------------- 282 @After 283 public void after() throws java.lang.Exception 284 { 285 try 286 { 287 // dispose our callback factory. This will automatically remove it from our service 288 // factory 289 m_callbackFactory.dispose(); 290 291 // revoke ourself as listener at the global event broadcaster 292 final XDocumentEventBroadcaster broadcaster = UnoRuntime.queryInterface(XDocumentEventBroadcaster.class, getMSF().createInstance("com.sun.star.frame.GlobalEventBroadcaster")); 293 broadcaster.removeDocumentEventListener(this); 294 } 295 catch (Exception e) 296 { 297 System.out.println("could not create the test case, error message:\n" + e.getMessage()); 298 e.printStackTrace(System.err); 299 fail("failed to close the test case"); 300 } 301 302 super.after(); 303 } 304 305 // -------------------------------------------------------------------------------------------------------- 306 private static class UnoMethodDescriptor 307 { 308 309 public Class unoInterfaceClass = null; 310 public String methodName = null; 311 312 UnoMethodDescriptor(Class _class, String _method) 313 { 314 unoInterfaceClass = _class; 315 methodName = _method; 316 } 317 } 318 319 // -------------------------------------------------------------------------------------------------------- 320 private void impl_checkDocumentInitState(Object _document, boolean _isInitialized) 321 { 322 // things you cannot do with an uninitialized document: 323 final UnoMethodDescriptor[] unsupportedMethods = new UnoMethodDescriptor[] 324 { 325 new UnoMethodDescriptor(XStorable.class, "store"), 326 new UnoMethodDescriptor(XFormDocumentsSupplier.class, "getFormDocuments"), 327 new UnoMethodDescriptor(XReportDocumentsSupplier.class, "getReportDocuments"), 328 new UnoMethodDescriptor(XScriptProviderSupplier.class, "getScriptProvider"), 329 new UnoMethodDescriptor(XEventsSupplier.class, "getEvents"), 330 new UnoMethodDescriptor(XTitle.class, "getTitle"), 331 new UnoMethodDescriptor(XModel2.class, "getControllers") 332 // (there's much more than this, but we cannot list all methods here, can we ...) 333 }; 334 335 for (int i = 0; i < unsupportedMethods.length; ++i) 336 { 337 assureException( _document, unsupportedMethods[i].unoInterfaceClass, 338 unsupportedMethods[i].methodName, new Object[]{}, _isInitialized ? null : NotInitializedException.class ); 339 } 340 } 341 342 // -------------------------------------------------------------------------------------------------------- 343 private XModel impl_createDocument() throws Exception 344 { 345 final XModel databaseDoc = UnoRuntime.queryInterface(XModel.class, getMSF().createInstance("com.sun.star.sdb.OfficeDatabaseDocument")); 346 347 // should not be initialized here - we did neither initNew nor load nor storeAsURL it 348 impl_checkDocumentInitState(databaseDoc, false); 349 350 return databaseDoc; 351 } 352 353 // -------------------------------------------------------------------------------------------------------- 354 private void impl_closeDocument(XModel _databaseDoc) throws CloseVetoException, IOException, Exception 355 { 356 final XCloseable closeDoc = UnoRuntime.queryInterface(XCloseable.class, _databaseDoc); 357 closeDoc.close(true); 358 } 359 360 // -------------------------------------------------------------------------------------------------------- 361 private XModel impl_createEmptyEmbeddedHSQLDocument() throws Exception, IOException 362 { 363 final XModel databaseDoc = UnoRuntime.queryInterface(XModel.class, getMSF().createInstance("com.sun.star.sdb.OfficeDatabaseDocument")); 364 final XStorable storeDoc = UnoRuntime.queryInterface(XStorable.class, databaseDoc); 365 366 // verify the document rejects API calls which require it to be initialized 367 impl_checkDocumentInitState(databaseDoc, false); 368 369 // though the document is not initialized, you can ask for the location, the URL, and the args 370 final String location = storeDoc.getLocation(); 371 final String url = databaseDoc.getURL(); 372 final PropertyValue[] args = databaseDoc.getArgs(); 373 // they should be all empty at this time 374 assertEquals("location is expected to be empty here", "", location); 375 assertEquals("URL is expected to be empty here", "", url); 376 assertEquals("Args are expected to be empty here", 0, args.length); 377 378 // and, you should be able to set properties at the data source 379 final XOfficeDatabaseDocument dataSourceAccess = UnoRuntime.queryInterface(XOfficeDatabaseDocument.class, databaseDoc); 380 final XPropertySet dsProperties = UnoRuntime.queryInterface(XPropertySet.class, dataSourceAccess.getDataSource()); 381 dsProperties.setPropertyValue("URL", "sdbc:embedded:hsqldb"); 382 383 final String documentURL = createTempFileURL(); 384 storeDoc.storeAsURL(documentURL, new PropertyValue[0]); 385 386 // now that the document is stored, ... 387 // ... its URL should be correct 388 assertEquals("wrong URL after storing the document", documentURL, databaseDoc.getURL()); 389 // ... it should be initialized 390 impl_checkDocumentInitState(databaseDoc, true); 391 392 return databaseDoc; 393 } 394 395 // -------------------------------------------------------------------------------------------------------- 396 @Test 397 public void testLoadable() throws Exception, IOException 398 { 399 XModel databaseDoc = impl_createEmptyEmbeddedHSQLDocument(); 400 String documentURL = databaseDoc.getURL(); 401 402 // there's three methods how you can initialize a database document: 403 404 // .................................................................... 405 // 1. XStorable::storeAsURL 406 // (this is for compatibility reasons, to not break existing code) 407 // this test is already made in impl_createEmptyEmbeddedHSQLDocument 408 409 // .................................................................... 410 // 2. XLoadable::load 411 databaseDoc = UnoRuntime.queryInterface(XModel.class, getMSF().createInstance("com.sun.star.sdb.OfficeDatabaseDocument")); 412 documentURL = copyToTempFile(documentURL); 413 // load the doc, and verify it's initialized then, and has the proper URL 414 XLoadable loadDoc = UnoRuntime.queryInterface(XLoadable.class, databaseDoc); 415 loadDoc.load(new PropertyValue[] 416 { 417 new PropertyValue("URL", 0, documentURL, PropertyState.DIRECT_VALUE) 418 }); 419 databaseDoc.attachResource(documentURL, new PropertyValue[0]); 420 421 assertEquals("wrong URL after loading the document", documentURL, databaseDoc.getURL()); 422 impl_checkDocumentInitState(databaseDoc, true); 423 424 // and while we are here ... initilizing the same document again should not be possible 425 assureException( databaseDoc, XLoadable.class, "initNew", new Object[0], 426 DoubleInitializationException.class ); 427 assureException( databaseDoc, XLoadable.class, "load", new Object[] { new PropertyValue[0] }, 428 DoubleInitializationException.class ); 429 430 // .................................................................... 431 // 3. XLoadable::initNew 432 impl_closeDocument(databaseDoc); 433 databaseDoc = impl_createDocument(); 434 loadDoc = UnoRuntime.queryInterface(XLoadable.class, databaseDoc); 435 loadDoc.initNew(); 436 assertEquals("wrong URL after initializing the document", "", databaseDoc.getURL()); 437 impl_checkDocumentInitState(databaseDoc, true); 438 439 // same as above - initializing the document a second time must fail 440 assureException( databaseDoc, XLoadable.class, "initNew", new Object[0], 441 DoubleInitializationException.class ); 442 assureException( databaseDoc, XLoadable.class, "load", new Object[] { new PropertyValue[0] }, 443 DoubleInitializationException.class ); 444 } 445 446 // -------------------------------------------------------------------------------------------------------- 447 private PropertyValue[] impl_getMarkerLoadArgs() 448 { 449 return new PropertyValue[] 450 { 451 new PropertyValue( "PickListEntry", 0, false, PropertyState.DIRECT_VALUE ), 452 new PropertyValue( "TestCase_Marker", 0, "Yes", PropertyState.DIRECT_VALUE ) 453 }; 454 } 455 456 // -------------------------------------------------------------------------------------------------------- 457 private boolean impl_hasMarker( final PropertyValue[] _args ) 458 { 459 for ( int i=0; i<_args.length; ++i ) 460 { 461 if ( _args[i].Name.equals( "TestCase_Marker" ) && _args[i].Value.equals( "Yes" ) ) 462 { 463 return true; 464 } 465 } 466 return false; 467 } 468 469 // -------------------------------------------------------------------------------------------------------- 470 private PropertyValue[] impl_getDefaultLoadArgs() 471 { 472 return new PropertyValue[] 473 { 474 new PropertyValue("PickListEntry", 0, false, PropertyState.DIRECT_VALUE) 475 }; 476 } 477 478 // -------------------------------------------------------------------------------------------------------- 479 private PropertyValue[] impl_getMacroExecLoadArgs() 480 { 481 return new PropertyValue[] 482 { 483 new PropertyValue("PickListEntry", 0, false, PropertyState.DIRECT_VALUE), 484 new PropertyValue("MacroExecutionMode", 0, com.sun.star.document.MacroExecMode.USE_CONFIG, PropertyState.DIRECT_VALUE), 485 new PropertyValue("InteractionHandler", 0, new MacroExecutionApprove(getMSF()), PropertyState.DIRECT_VALUE) 486 }; 487 } 488 489 // -------------------------------------------------------------------------------------------------------- 490 private int impl_setMacroSecurityLevel(int _level) throws Exception 491 { 492 final XMultiServiceFactory configProvider = UnoRuntime.queryInterface(XMultiServiceFactory.class, getMSF().createInstance("com.sun.star.configuration.ConfigurationProvider")); 493 494 final PropertyValue[] args = new PropertyValue[] 495 { 496 new PropertyValue("nodepath", 0, "/org.openoffice.Office.Common/Security/Scripting", PropertyState.DIRECT_VALUE) 497 }; 498 499 final XPropertySet securitySettings = UnoRuntime.queryInterface(XPropertySet.class, configProvider.createInstanceWithArguments("com.sun.star.configuration.ConfigurationUpdateAccess", args)); 500 final int oldValue = ((Integer) securitySettings.getPropertyValue("MacroSecurityLevel")).intValue(); 501 securitySettings.setPropertyValue("MacroSecurityLevel", Integer.valueOf(_level)); 502 503 final XChangesBatch committer = UnoRuntime.queryInterface(XChangesBatch.class, securitySettings); 504 committer.commitChanges(); 505 506 return oldValue; 507 } 508 509 // -------------------------------------------------------------------------------------------------------- 510 private XModel impl_loadDocument( final String _documentURL, final PropertyValue[] _loadArgs ) throws Exception 511 { 512 final XComponentLoader loader = UnoRuntime.queryInterface(XComponentLoader.class, getMSF().createInstance("com.sun.star.frame.Desktop")); 513 return UnoRuntime.queryInterface(XModel.class, loader.loadComponentFromURL(_documentURL, _BLANK, 0, _loadArgs)); 514 } 515 516 // -------------------------------------------------------------------------------------------------------- 517 private void impl_storeDocument( final XModel _document ) throws Exception, IOException 518 { 519 // store the document 520 final String documentURL = FileHelper.getOOoCompatibleFileURL( _document.getURL() ); 521 final XStorable storeDoc = UnoRuntime.queryInterface(XStorable.class, _document); 522 storeDoc.store(); 523 524 } 525 526 // -------------------------------------------------------------------------------------------------------- 527 private XModel impl_createDocWithMacro( final String _libName, final String _moduleName, final String _code ) throws Exception, IOException 528 { 529 // create an empty document 530 XModel databaseDoc = impl_createEmptyEmbeddedHSQLDocument(); 531 532 // create Basic library/module therein 533 final XEmbeddedScripts embeddedScripts = UnoRuntime.queryInterface(XEmbeddedScripts.class, databaseDoc); 534 final XStorageBasedLibraryContainer basicLibs = embeddedScripts.getBasicLibraries(); 535 final XNameContainer newLib = basicLibs.createLibrary( _libName ); 536 newLib.insertByName( _moduleName, _code ); 537 538 return databaseDoc; 539 } 540 541 // -------------------------------------------------------------------------------------------------------- 542 /** tests various aspects of database document "revenants" 543 * 544 * Well, I do not really have a good term for this ... The point is, database documents are in real 545 * only *one* aspect of a more complex thing. The second aspect is a data source. Both, in some sense, 546 * just represent different views on the same thing. For a given database, there's at each time at most 547 * one data source, and at most one database document. Both have a independent life time, and are 548 * created when needed. 549 * In particular, a document can be closed (this is what happens when the last UI window displaying 550 * this document is closed), and then dies. Now when the other "view", the data source, still exists, 551 * the the underlying document data is not discarded, but kept alive (else the data source would die 552 * just because the document dies, which is not desired). If the document is loaded, again, then 553 * it is re-created, using the data of its previous "incarnation". 554 * 555 * This method here tests some of those aspects of a document which should survive the death of one 556 * instance and re-creation as a revenant. 557 */ 558 @Test 559 public void testDocumentRevenants() throws Exception, IOException 560 { 561 // create an empty document 562 XModel databaseDoc = impl_createDocWithMacro( "Lib", "Module", 563 "Sub Hello\n" + 564 " MsgBox \"Hello\"\n" + 565 "End Sub\n" 566 ); 567 impl_storeDocument( databaseDoc ); 568 final String documentURL = databaseDoc.getURL(); 569 570 // at this stage, the marker should not yet be present in the doc's args, else some of the below 571 // tests become meaningless 572 assertTrue( "A newly created doc should not have the test case marker", !impl_hasMarker( databaseDoc.getArgs() ) ); 573 574 // obtain the DataSource associated with the document. Keeping this alive 575 // ensures that the "impl data" of the document is kept alive, too, so when closing 576 // and re-opening it, this "impl data" must be re-used. 577 XDocumentDataSource dataSource = UnoRuntime.queryInterface(XDocumentDataSource.class, (UnoRuntime.queryInterface(XOfficeDatabaseDocument.class, databaseDoc)).getDataSource()); 578 579 // close and reload the doc 580 impl_closeDocument(databaseDoc); 581 databaseDoc = impl_loadDocument( documentURL, impl_getMarkerLoadArgs() ); 582 // since we just put the marker into the load-call, it should be present at the doc 583 assertTrue( "The test case marker got lost.", impl_hasMarker( databaseDoc.getArgs() ) ); 584 585 // The basic library should have survived 586 final XEmbeddedScripts embeddedScripts = UnoRuntime.queryInterface(XEmbeddedScripts.class, databaseDoc); 587 final XStorageBasedLibraryContainer basicLibs = embeddedScripts.getBasicLibraries(); 588 assertTrue( "Baisc lib did not survive reloading a closed document", basicLibs.hasByName( "Lib" ) ); 589 final XNameContainer lib = UnoRuntime.queryInterface(XNameContainer.class, basicLibs.getByName("Lib")); 590 assertTrue( "Basic module did not survive reloading a closed document", lib.hasByName( "Module" ) ); 591 592 // now closing the doc, and obtaining it from the data source, should preserve the marker we put into the load 593 // args 594 impl_closeDocument( databaseDoc ); 595 databaseDoc = UnoRuntime.queryInterface(XModel.class, dataSource.getDatabaseDocument()); 596 assertTrue( "The test case marker did not survive re-retrieval of the doc from the data source.", 597 impl_hasMarker( databaseDoc.getArgs() ) ); 598 599 // on the other hand, closing and regurlarly re-loading the doc *without* the marker should indeed 600 // lose it 601 impl_closeDocument( databaseDoc ); 602 databaseDoc = impl_loadDocument( documentURL, impl_getDefaultLoadArgs() ); 603 assertTrue( "Reloading the document kept the old args, instead of the newly supplied ones.", 604 !impl_hasMarker( databaseDoc.getArgs() ) ); 605 606 // clean up 607 impl_closeDocument( databaseDoc ); 608 } 609 610 // -------------------------------------------------------------------------------------------------------- 611 @Test 612 public void testDocumentEvents() throws Exception, IOException 613 { 614 // create an empty document 615 final String libName = "EventHandlers"; 616 final String moduleName = "all"; 617 final String eventHandlerCode = 618 "Option Explicit\n" + 619 "\n" + 620 "Sub OnLoad\n" + 621 " Dim oCallback as Object\n" + 622 " oCallback = createUnoService( \"" + getCallbackComponentServiceName() + "\" )\n" + 623 "\n" + 624 " ' as long as the Document is not passed to the Basic callbacks, we need to create\n" + 625 " ' one ourself\n" + 626 " Dim oEvent as new com.sun.star.document.DocumentEvent\n" + 627 " oEvent.EventName = \"OnLoad\"\n" + 628 " oEvent.Source = ThisComponent\n" + 629 "\n" + 630 " oCallback.documentEventOccured( oEvent )\n" + 631 "End Sub\n"; 632 XModel databaseDoc = impl_createDocWithMacro( libName, moduleName, eventHandlerCode ); 633 final String documentURL = databaseDoc.getURL(); 634 635 // bind the macro to the OnLoad event 636 final String macroURI = "vnd.sun.star.script:" + libName + "." + moduleName + ".OnLoad?language=Basic&location=document"; 637 final XEventsSupplier eventsSupplier = UnoRuntime.queryInterface(XEventsSupplier.class, databaseDoc); 638 eventsSupplier.getEvents().replaceByName("OnLoad", new PropertyValue[] 639 { 640 new PropertyValue("EventType", 0, "Script", PropertyState.DIRECT_VALUE), 641 new PropertyValue("Script", 0, macroURI, PropertyState.DIRECT_VALUE) 642 }); 643 644 // store the document, and close it 645 impl_storeDocument( databaseDoc ); 646 impl_closeDocument( databaseDoc ); 647 648 // ensure the macro security configuration is "ask the user for document macro execution" 649 final int oldSecurityLevel = impl_setMacroSecurityLevel(1); 650 651 // load it, again 652 m_loadDocState = STATE_LOADING_DOC; 653 // expected order of states is: 654 // STATE_LOADING_DOC - initialized here 655 // STATE_MACRO_EXEC_APPROVED - done in our interaction handler, which auto-approves the execution of macros 656 // STATE_ON_LOAD_RECEIVED - done in our callback for the document events 657 // 658 // In particular, it is important that the interaction handler (which plays the role of the user confirmation 659 // here) is called before the OnLoad notification is received - since the latter happens from within 660 // a Basic macro which is bound to the OnLoad event of the document. 661 662 final String context = "OnLoad"; 663 impl_startObservingEvents(context); 664 databaseDoc = impl_loadDocument( documentURL, impl_getMacroExecLoadArgs() ); 665 impl_stopObservingEvents(m_documentEvents, new String[] 666 { 667 "OnLoad" 668 }, context); 669 670 assertEquals("our provided interaction handler was not called", STATE_ON_LOAD_RECEIVED, m_loadDocState); 671 672 // restore macro security level 673 impl_setMacroSecurityLevel(oldSecurityLevel); 674 675 // close the document 676 impl_closeDocument(databaseDoc); 677 } 678 679 // -------------------------------------------------------------------------------------------------------- 680 @Test 681 public void testGlobalEvents() throws Exception, IOException 682 { 683 XModel databaseDoc = impl_createEmptyEmbeddedHSQLDocument(); 684 final XStorable storeDoc = UnoRuntime.queryInterface(XStorable.class, databaseDoc); 685 686 String context, newURL; 687 688 // XStorable.store 689 final String oldURL = databaseDoc.getURL(); 690 context = "store"; 691 impl_startObservingEvents(context); 692 storeDoc.store(); 693 assertEquals("store is not expected to change the document URL", databaseDoc.getURL(), oldURL); 694 impl_stopObservingEvents(m_globalEvents, new String[] 695 { 696 "OnSave", "OnSaveDone" 697 }, context); 698 699 // XStorable.storeToURL 700 context = "storeToURL"; 701 impl_startObservingEvents(context); 702 storeDoc.storeToURL(createTempFileURL(), new PropertyValue[0]); 703 assertEquals("storetoURL is not expected to change the document URL", databaseDoc.getURL(), oldURL); 704 impl_stopObservingEvents(m_globalEvents, new String[] 705 { 706 "OnSaveTo", "OnSaveToDone" 707 }, context); 708 709 // XStorable.storeAsURL 710 newURL = createTempFileURL(); 711 context = "storeAsURL"; 712 impl_startObservingEvents(context); 713 storeDoc.storeAsURL(newURL, new PropertyValue[0]); 714 assertEquals("storeAsURL is expected to change the document URL", databaseDoc.getURL(), newURL); 715 impl_stopObservingEvents(m_globalEvents, new String[] 716 { 717 "OnSaveAs", "OnSaveAsDone" 718 }, context); 719 720 // XModifiable.setModified 721 final XModifiable modifyDoc = UnoRuntime.queryInterface(XModifiable.class, databaseDoc); 722 context = "setModified"; 723 impl_startObservingEvents(context); 724 modifyDoc.setModified(true); 725 assertEquals("setModified didn't work", modifyDoc.isModified(), true); 726 impl_stopObservingEvents(m_globalEvents, new String[] 727 { 728 "OnModifyChanged" 729 }, context); 730 731 // XStorable.store, with implicit reset of the "Modified" flag 732 context = "store (2)"; 733 impl_startObservingEvents(context); 734 storeDoc.store(); 735 assertEquals("'store' should implicitly reset the modified flag", modifyDoc.isModified(), false); 736 impl_stopObservingEvents(m_globalEvents, new String[] 737 { 738 "OnSave", "OnSaveDone", "OnModifyChanged" 739 }, context); 740 741 // XComponentLoader.loadComponentFromURL 742 newURL = copyToTempFile(databaseDoc.getURL()); 743 final XComponentLoader loader = UnoRuntime.queryInterface(XComponentLoader.class, getMSF().createInstance("com.sun.star.frame.Desktop")); 744 context = "loadComponentFromURL"; 745 impl_startObservingEvents(context); 746 databaseDoc = UnoRuntime.queryInterface(XModel.class, loader.loadComponentFromURL(newURL, _BLANK, 0, impl_getDefaultLoadArgs())); 747 impl_stopObservingEvents(m_globalEvents, 748 new String[] 749 { 750 "OnLoadFinished", "OnViewCreated", "OnFocus", "OnLoad" 751 }, context); 752 753 // closing a document by API 754 final XCloseable closeDoc = UnoRuntime.queryInterface(XCloseable.class, databaseDoc); 755 context = "close (API)"; 756 impl_startObservingEvents(context); 757 closeDoc.close(true); 758 impl_stopObservingEvents(m_globalEvents, 759 new String[] 760 { 761 "OnPrepareUnload", "OnViewClosed", "OnUnload" 762 }, context); 763 764 // closing a document via UI 765 context = "close (UI)"; 766 impl_startObservingEvents("prepare for '" + context + "'"); 767 databaseDoc = UnoRuntime.queryInterface(XModel.class, loader.loadComponentFromURL(newURL, _BLANK, 0, impl_getDefaultLoadArgs())); 768 impl_waitForEvent(m_globalEvents, "OnLoad", 5000); 769 // wait for all events to arrive - OnLoad should be the last one 770 771 final XDispatchProvider dispatchProvider = UnoRuntime.queryInterface(XDispatchProvider.class, databaseDoc.getCurrentController().getFrame()); 772 final URL url = impl_getURL(".uno:CloseDoc"); 773 final XDispatch dispatcher = dispatchProvider.queryDispatch(url, "", 0); 774 impl_startObservingEvents(context); 775 dispatcher.dispatch(url, new PropertyValue[0]); 776 impl_stopObservingEvents(m_globalEvents, 777 new String[] 778 { 779 "OnPrepareViewClosing", "OnViewClosed", "OnPrepareUnload", "OnUnload" 780 }, context); 781 782 // creating a new document 783 databaseDoc = impl_createDocument(); 784 final XLoadable loadDoc = UnoRuntime.queryInterface(XLoadable.class, databaseDoc); 785 context = "initNew"; 786 impl_startObservingEvents(context); 787 loadDoc.initNew(); 788 impl_stopObservingEvents(m_globalEvents, new String[] 789 { 790 "OnCreate" 791 }, context); 792 793 impl_startObservingEvents(context + " (cleanup)"); 794 impl_closeDocument(databaseDoc); 795 impl_waitForEvent(m_globalEvents, "OnUnload", 5000); 796 797 // focus changes 798 context = "activation"; 799 // for this, load a database document ... 800 impl_startObservingEvents("prepare for '" + context + "'"); 801 databaseDoc = UnoRuntime.queryInterface(XModel.class, loader.loadComponentFromURL(newURL, _BLANK, 0, impl_getDefaultLoadArgs())); 802 final int previousOnLoadEventPos = impl_waitForEvent(m_globalEvents, "OnLoad", 5000); 803 // ... and another document ... 804 final String otherURL = copyToTempFile(databaseDoc.getURL()); 805 final XModel otherDoc = UnoRuntime.queryInterface(XModel.class, loader.loadComponentFromURL(otherURL, _BLANK, 0, impl_getDefaultLoadArgs())); 806 impl_raise(otherDoc); 807 impl_waitForEvent(m_globalEvents, "OnLoad", 5000, previousOnLoadEventPos + 1); 808 809 // ... and switch between the two 810 impl_startObservingEvents(context); 811 impl_raise(databaseDoc); 812 impl_stopObservingEvents(m_globalEvents, new String[] 813 { 814 "OnUnfocus", "OnFocus" 815 }, context); 816 817 // cleanup 818 impl_startObservingEvents("cleanup after '" + context + "'"); 819 impl_closeDocument(databaseDoc); 820 impl_closeDocument(otherDoc); 821 } 822 823 // -------------------------------------------------------------------------------------------------------- 824 private URL impl_getURL(String _completeURL) throws Exception 825 { 826 final URL[] url = 827 { 828 new URL() 829 }; 830 url[0].Complete = _completeURL; 831 final XURLTransformer urlTransformer = UnoRuntime.queryInterface(XURLTransformer.class, getMSF().createInstance("com.sun.star.util.URLTransformer")); 832 urlTransformer.parseStrict(url); 833 return url[0]; 834 } 835 836 // -------------------------------------------------------------------------------------------------------- 837 private void impl_raise(XModel _document) 838 { 839 final XFrame frame = _document.getCurrentController().getFrame(); 840 final XTopWindow topWindow = UnoRuntime.queryInterface(XTopWindow.class, frame.getContainerWindow()); 841 topWindow.toFront(); 842 } 843 844 // -------------------------------------------------------------------------------------------------------- 845 private void impl_startObservingEvents(String _context) 846 { 847 System.out.println(" " + _context + " {"); 848 synchronized (m_documentEvents) 849 { 850 m_documentEvents.clear(); 851 } 852 synchronized (m_globalEvents) 853 { 854 m_globalEvents.clear(); 855 } 856 } 857 858 // -------------------------------------------------------------------------------------------------------- 859 private void impl_stopObservingEvents(ArrayList _actualEvents, String[] _expectedEvents, String _context) 860 { 861 try 862 { 863 synchronized (_actualEvents) 864 { 865 int actualEventCount = _actualEvents.size(); 866 while (actualEventCount < _expectedEvents.length) 867 { 868 // well, it's possible not all events already arrived, yet - finally, some of them 869 // are notified asynchronously 870 // So, wait a few seconds. 871 try 872 { 873 _actualEvents.wait(20000); 874 } 875 catch (InterruptedException ex) 876 { 877 } 878 879 if (actualEventCount == _actualEvents.size()) 880 // the above wait was left because of the timeout, *not* because an event 881 // arrived. Okay, we won't wait any longer, this is a failure. 882 { 883 break; 884 } 885 actualEventCount = _actualEvents.size(); 886 } 887 888 assertEquals("wrong event count for '" + _context + "'", 889 _expectedEvents.length, _actualEvents.size()); 890 891 for (int i = 0; i < _expectedEvents.length; ++i) 892 { 893 assertEquals("wrong event at positon " + (i + 1) + " for '" + _context + "'", 894 _expectedEvents[i], _actualEvents.get(i)); 895 } 896 } 897 } 898 finally 899 { 900 System.out.println(" }"); 901 } 902 } 903 904 // -------------------------------------------------------------------------------------------------------- 905 int impl_waitForEvent(ArrayList _eventQueue, String _expectedEvent, int _maxMilliseconds) 906 { 907 return impl_waitForEvent(_eventQueue, _expectedEvent, _maxMilliseconds, 0); 908 } 909 910 // -------------------------------------------------------------------------------------------------------- 911 int impl_waitForEvent(ArrayList _eventQueue, String _expectedEvent, int _maxMilliseconds, int _firstQueueElementToCheck) 912 { 913 synchronized (_eventQueue) 914 { 915 int waitedMilliseconds = 0; 916 917 while (waitedMilliseconds < _maxMilliseconds) 918 { 919 for (int i = _firstQueueElementToCheck; i < _eventQueue.size(); ++i) 920 { 921 if (_expectedEvent.equals(_eventQueue.get(i))) 922 // found the event in the queue 923 { 924 return i; 925 } 926 } 927 928 // wait a little, perhaps the event will still arrive 929 try 930 { 931 _eventQueue.wait(500); 932 waitedMilliseconds += 500; 933 } 934 catch (InterruptedException e) 935 { 936 } 937 } 938 } 939 940 fail("expected event '" + _expectedEvent + "' did not arrive after " + _maxMilliseconds + " milliseconds"); 941 return -1; 942 } 943 944 // -------------------------------------------------------------------------------------------------------- 945 void onDocumentEvent(DocumentEvent _Event) 946 { 947 if ("OnTitleChanged".equals(_Event.EventName)) 948 // OnTitleChanged events are notified too often. This is known, and accepted. 949 // (the deeper reason is that it's diffult to determine, in the DatabaseDocument implementatin, 950 // when the title actually changed. In particular, when we do a saveAsURL, and then ask for a 951 // title *before* the TitleHelper got the document's OnSaveAsDone event, then the wrong (old) 952 // title is obtained. 953 { 954 return; 955 } 956 957 if ((_Event.EventName.equals("OnLoad")) && (m_loadDocState != STATE_NOT_STARTED)) 958 { 959 assertEquals("OnLoad event must come *after* invocation of the interaction handler / user!", 960 m_loadDocState, STATE_MACRO_EXEC_APPROVED); 961 m_loadDocState = STATE_ON_LOAD_RECEIVED; 962 } 963 964 synchronized (m_documentEvents) 965 { 966 m_documentEvents.add(_Event.EventName); 967 m_documentEvents.notifyAll(); 968 } 969 970 System.out.println(" document event: " + _Event.EventName); 971 } 972 973 // -------------------------------------------------------------------------------------------------------- 974 public void documentEventOccured(DocumentEvent _Event) 975 { 976 if ("OnTitleChanged".equals(_Event.EventName)) 977 // ignore. See onDocumentEvent for a justification 978 { 979 return; 980 } 981 982 synchronized (m_globalEvents) 983 { 984 m_globalEvents.add(_Event.EventName); 985 m_globalEvents.notifyAll(); 986 } 987 988 System.out.println(" global event: " + _Event.EventName); 989 } 990 991 // -------------------------------------------------------------------------------------------------------- 992 public void disposing(EventObject _Event) 993 { 994 // not interested in 995 } 996 } 997