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