diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-15 05:54:39 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-15 05:54:39 +0000 |
commit | 267c6f2ac71f92999e969232431ba04678e7437e (patch) | |
tree | 358c9467650e1d0a1d7227a21dac2e3d08b622b2 /dbaccess/qa/complex | |
parent | Initial commit. (diff) | |
download | libreoffice-267c6f2ac71f92999e969232431ba04678e7437e.tar.xz libreoffice-267c6f2ac71f92999e969232431ba04678e7437e.zip |
Adding upstream version 4:24.2.0.upstream/4%24.2.0
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'dbaccess/qa/complex')
19 files changed, 4373 insertions, 0 deletions
diff --git a/dbaccess/qa/complex/dbaccess/ApplicationController.java b/dbaccess/qa/complex/dbaccess/ApplicationController.java new file mode 100644 index 0000000000..eafc32dba6 --- /dev/null +++ b/dbaccess/qa/complex/dbaccess/ApplicationController.java @@ -0,0 +1,159 @@ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ +package complex.dbaccess; + +import com.sun.star.beans.PropertyValue; +import com.sun.star.container.XNameAccess; +import com.sun.star.frame.FrameSearchFlag; +import com.sun.star.frame.XComponentLoader; +import com.sun.star.frame.XModel; +import com.sun.star.frame.XStorable; +import com.sun.star.lang.XComponent; +import com.sun.star.sdb.XOfficeDatabaseDocument; +import com.sun.star.sdb.application.XDatabaseDocumentUI; +import com.sun.star.sdbcx.XTablesSupplier; +import com.sun.star.uno.Exception; +import com.sun.star.uno.UnoRuntime; +import connectivity.tools.HsqlColumnDescriptor; +import connectivity.tools.HsqlDatabase; +import connectivity.tools.HsqlTableDescriptor; +import java.io.IOException; + + +// ---------- junit imports ----------------- +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import static org.junit.Assert.*; + + +/** complex test case for Base's application UI + */ +public class ApplicationController extends TestCase +{ + + private HsqlDatabase m_database; + private XOfficeDatabaseDocument m_databaseDocument; + private XDatabaseDocumentUI m_documentUI; + + public String getTestObjectName() + { + return getClass().getName(); + } + + + private void impl_closeDocument() + { + if (m_database != null) + { + m_database.close(); + m_database = null; + m_databaseDocument = null; + m_documentUI = null; + } + } + + + private void impl_switchToDocument(String _documentURL) throws java.lang.Exception + { + // close previous database document + impl_closeDocument(); + + // create/load the new database document + m_database = (_documentURL == null) + ? new HsqlDatabase(getMSF()) + : new HsqlDatabase(getMSF(), _documentURL); + m_databaseDocument = m_database.getDatabaseDocument(); + + // load it into a frame + final Object object = getMSF().createInstance("com.sun.star.frame.Desktop"); + final XComponentLoader xComponentLoader = UnoRuntime.queryInterface(XComponentLoader.class, object); + final XComponent loadedComponent = xComponentLoader.loadComponentFromURL(m_database.getDocumentURL(), "_blank", FrameSearchFlag.ALL, new PropertyValue[0]); + + assertTrue("too many document instances!", + UnoRuntime.areSame(loadedComponent, m_databaseDocument)); + + // get the controller, which provides access to various UI operations + final XModel docModel = UnoRuntime.queryInterface(XModel.class, + loadedComponent); + m_documentUI = UnoRuntime.queryInterface(XDatabaseDocumentUI.class, + docModel.getCurrentController()); + } + + + @Before + @Override + public void before() throws java.lang.Exception + { + super.before(); + impl_switchToDocument(null); + } + + + @After + @Override + public void after() throws java.lang.Exception + { + impl_closeDocument(); + super.after(); + } + + + @Test + public void checkSaveAs() throws Exception, IOException, java.lang.Exception + { + // issue 93737 describes the problem that when you save-as a database document, and do changes to it, + // then those changes are saved in the old document, actually + final String oldDocumentURL = m_database.getDocumentURL(); + + final String newDocumentURL = createTempFileURL(); + + // store the doc in a new location + final XStorable storeDoc = UnoRuntime.queryInterface( XStorable.class, m_databaseDocument ); + storeDoc.storeAsURL( newDocumentURL, new PropertyValue[] { } ); + + // connect + m_documentUI.connect(); + assertTrue("could not connect to " + m_database.getDocumentURL(), m_documentUI.isConnected()); + + // create a table in the database + m_database.createTable(new HsqlTableDescriptor("abc", new HsqlColumnDescriptor[] + { + new HsqlColumnDescriptor("a", "VARCHAR(50)"), + new HsqlColumnDescriptor("b", "VARCHAR(50)"), + new HsqlColumnDescriptor("c", "VARCHAR(50)") + })); + + // load the old document, and verify there is *no* table therein + impl_switchToDocument(oldDocumentURL); + m_documentUI.connect(); + assertTrue("could not connect to " + m_database.getDocumentURL(), m_documentUI.isConnected()); + XTablesSupplier suppTables = UnoRuntime.queryInterface( XTablesSupplier.class, m_documentUI.getActiveConnection() ); + XNameAccess tables = suppTables.getTables(); + assertTrue("the table was created in the wrong database", !tables.hasByName("abc")); + + // load the new document, and verify there *is* a table therein + impl_switchToDocument(newDocumentURL); + m_documentUI.connect(); + assertTrue("could not connect to " + m_database.getDocumentURL(), m_documentUI.isConnected()); + + suppTables = UnoRuntime.queryInterface( XTablesSupplier.class, m_documentUI.getActiveConnection() ); + tables = suppTables.getTables(); + assertTrue("the newly created table has not been written", tables.hasByName("abc")); + } +} diff --git a/dbaccess/qa/complex/dbaccess/Beamer.java b/dbaccess/qa/complex/dbaccess/Beamer.java new file mode 100644 index 0000000000..3342c70dff --- /dev/null +++ b/dbaccess/qa/complex/dbaccess/Beamer.java @@ -0,0 +1,127 @@ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ +package complex.dbaccess; + +import com.sun.star.beans.PropertyState; +import com.sun.star.beans.PropertyValue; +import com.sun.star.container.XEnumeration; +import com.sun.star.frame.FrameSearchFlag; +import com.sun.star.frame.XComponentLoader; +import com.sun.star.frame.XController; +import com.sun.star.frame.XDispatch; +import com.sun.star.frame.XDispatchProvider; +import com.sun.star.frame.XFrame; +import com.sun.star.frame.XGlobalEventBroadcaster; +import com.sun.star.frame.XModel; +import com.sun.star.frame.theGlobalEventBroadcaster; +import com.sun.star.lang.XComponent; +import com.sun.star.sdb.CommandType; +import com.sun.star.uno.Exception; +import com.sun.star.uno.UnoRuntime; +import com.sun.star.util.URL; +import com.sun.star.util.XURLTransformer; +import com.sun.star.view.XSelectionSupplier; +// ---------- junit imports ----------------- +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import static org.junit.Assert.*; + + + +/** complex test case for Base's application UI + */ +public class Beamer extends TestCase +{ + + private XModel docModel; + + @Before + @Override + public void before() throws Exception, java.lang.Exception + { + // load it into a frame + final Object object = getMSF().createInstance("com.sun.star.frame.Desktop"); + final XComponentLoader xComponentLoader = UnoRuntime.queryInterface(XComponentLoader.class, object); + final XComponent loadedComponent = xComponentLoader.loadComponentFromURL("private:factory/swriter", "_blank", 0, new PropertyValue[0]); + // get the controller, which provides access to various UI operations + docModel = UnoRuntime.queryInterface(XModel.class, loadedComponent); + } + + + @After + @Override + public void after() + { + } + + + @Test + public void testBeamer() throws Exception, java.lang.Exception + { + final XController controller = docModel.getCurrentController(); + final XFrame frame = controller.getFrame(); + final XDispatchProvider dispatchP = UnoRuntime.queryInterface(XDispatchProvider.class, frame); + URL command = new URL(); + command.Complete = ".uno:ViewDataSourceBrowser"; + + Object instance = getMSF().createInstance("com.sun.star.util.URLTransformer"); + XURLTransformer atrans = UnoRuntime.queryInterface(XURLTransformer.class, instance); + com.sun.star.util.URL[] aURLA = new com.sun.star.util.URL[1]; + aURLA[0] = command; + atrans.parseStrict(aURLA); + command = aURLA[0]; + + final XDispatch dispatch = dispatchP.queryDispatch(command, "_self", FrameSearchFlag.AUTO); + assertNotNull(dispatch); + dispatch.dispatch(command, new PropertyValue[0]); + + final PropertyValue[] props = new PropertyValue[] + { + new PropertyValue("DataSourceName", 0, "Bibliography", PropertyState.DIRECT_VALUE), + new PropertyValue("CommandType", 0, Integer.valueOf(CommandType.TABLE), PropertyState.DIRECT_VALUE), + new PropertyValue("Command", 0, "biblio", PropertyState.DIRECT_VALUE) + }; + + final XFrame beamer = frame.findFrame("_beamer", 0); + assertNotNull(beamer); + final XGlobalEventBroadcaster evtBc = theGlobalEventBroadcaster.get( + getComponentContext()); + XEnumeration enumeration = evtBc.createEnumeration(); + int count = -1; + while (enumeration.hasMoreElements()) + { + enumeration.nextElement(); + ++count; + } + final XSelectionSupplier selSup = UnoRuntime.queryInterface(XSelectionSupplier.class, beamer.getController()); + selSup.select(props); + final com.sun.star.util.XCloseable close = UnoRuntime.queryInterface(com.sun.star.util.XCloseable.class, frame); + close.close(false); + + enumeration = evtBc.createEnumeration(); + int count2 = 0; + while (enumeration.hasMoreElements()) + { + enumeration.nextElement(); + ++count2; + } + + assertTrue("count1 = " + count + " count2 = " + count2, count == count2); + } +} diff --git a/dbaccess/qa/complex/dbaccess/CRMBasedTestCase.java b/dbaccess/qa/complex/dbaccess/CRMBasedTestCase.java new file mode 100644 index 0000000000..a61ffdfa74 --- /dev/null +++ b/dbaccess/qa/complex/dbaccess/CRMBasedTestCase.java @@ -0,0 +1,63 @@ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ +package complex.dbaccess; + +import com.sun.star.sdb.XSingleSelectQueryComposer; +import connectivity.tools.CRMDatabase; + +// ---------- junit imports ----------------- +import org.junit.After; +import org.junit.Before; + + +public abstract class CRMBasedTestCase extends TestCase +{ + protected CRMDatabase m_database; + + protected void createTestCase() throws Exception + { + m_database = new CRMDatabase( getMSF(), false ); + } + + + @Before + @Override + public void before() throws Exception + { + createTestCase(); + } + + + @After + @Override + public void after() throws Exception + { + if ( m_database != null ) + { + m_database.saveAndClose(); + } + } + + + /** creates a SingleSelectQueryComposer for our connection + */ + protected final XSingleSelectQueryComposer createQueryComposer() throws com.sun.star.uno.Exception + { + return m_database.getConnection().createSingleSelectQueryComposer(); + } +} diff --git a/dbaccess/qa/complex/dbaccess/CopyTableInterActionHandler.java b/dbaccess/qa/complex/dbaccess/CopyTableInterActionHandler.java new file mode 100644 index 0000000000..57f4172e3b --- /dev/null +++ b/dbaccess/qa/complex/dbaccess/CopyTableInterActionHandler.java @@ -0,0 +1,37 @@ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ +package complex.dbaccess; + +import com.sun.star.lib.uno.helper.WeakBase; +import com.sun.star.task.XInteractionHandler; +import com.sun.star.task.XInteractionRequest; + +import static org.junit.Assert.*; + +class CopyTableInterActionHandler extends WeakBase + implements XInteractionHandler +{ + CopyTableInterActionHandler() + { + } + + public void handle(XInteractionRequest xRequest) + { + fail( "interaction handler is not expected to be called" ); + } +} diff --git a/dbaccess/qa/complex/dbaccess/CopyTableWizard.java b/dbaccess/qa/complex/dbaccess/CopyTableWizard.java new file mode 100644 index 0000000000..f2620f645e --- /dev/null +++ b/dbaccess/qa/complex/dbaccess/CopyTableWizard.java @@ -0,0 +1,194 @@ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ +package complex.dbaccess; + +import com.sun.star.accessibility.XAccessible; +import com.sun.star.accessibility.XAccessibleContext; +import com.sun.star.awt.XExtendedToolkit; +import com.sun.star.awt.XWindow; +import com.sun.star.beans.Optional; +import com.sun.star.beans.XPropertySet; +import com.sun.star.container.XNameAccess; +import com.sun.star.sdb.CommandType; +import com.sun.star.sdb.application.XCopyTableWizard; +import com.sun.star.sdb.DataAccessDescriptorFactory; +import com.sun.star.sdbc.XConnection; +import com.sun.star.sdbcx.XTablesSupplier; +import com.sun.star.task.XInteractionHandler; +import com.sun.star.uno.UnoRuntime; +import com.sun.star.uno.XComponentContext; +import connectivity.tools.DbaseDatabase; +import java.io.IOException; +import util.UITools; + +// ---------- junit imports ----------------- +import org.junit.After; +import org.junit.Before; +import org.junit.Test; + + +/** complex test case for Base's application UI + */ +public class CopyTableWizard extends CRMBasedTestCase +{ + + private DatabaseApplication source; + private DbaseDatabase destinationDB = null; + private DatabaseApplication dest; + + @After + @Override + public void after() throws Exception + { + dest.store(); + if ( destinationDB != null ) + destinationDB.close(); + destinationDB = null; + super.after(); + } + + @Before + @Override + public void before() throws Exception + { + createTestCase(); + source = new DatabaseApplication(m_database.getDatabase()); + destinationDB = new DbaseDatabase( getMSF() ); + dest = new DatabaseApplication( destinationDB ); + } + + + private static class CopyThread implements Runnable + { + + private final XCopyTableWizard copyWizard; + + private CopyThread(final XCopyTableWizard copyWizard) + { + this.copyWizard = copyWizard; + } + + public void run() + { + copyWizard.execute(); + } + } + + private XWindow getActiveWindow() + { + Object toolKit = null; + try + { + toolKit = getMSF().createInstance("com.sun.star.awt.Toolkit"); + } + catch (com.sun.star.uno.Exception e) + { + return null; + } + + XExtendedToolkit tk = UnoRuntime.queryInterface( XExtendedToolkit.class, toolKit ); + Object atw = tk.getActiveTopWindow(); + return UnoRuntime.queryInterface( XWindow.class, atw ); + } + + @Test + public void copyTable() throws Exception, IOException, java.lang.Exception + { + copyTable(source,source); + } + + @Test + public void copyTableDbase() throws Exception, IOException, java.lang.Exception + { + copyTable(source,dest); + } + private void copyTable(final DatabaseApplication sourceDb,final DatabaseApplication destDb) throws Exception, IOException, java.lang.Exception + { + final XConnection destConnection = destDb.getDocumentUI().getActiveConnection(); + + final XConnection sourceConnection = sourceDb.getDocumentUI().getActiveConnection(); + final XTablesSupplier suppTables = UnoRuntime.queryInterface(XTablesSupplier.class, sourceConnection); + final XNameAccess tables = suppTables.getTables(); + + final String[] names = tables.getElementNames(); + for (int i = 0; i < names.length; i++) + { + copyTable(names[i], sourceConnection, destConnection); + } + } + + private void copyTable(final String tableName, final XConnection sourceConnection, final XConnection destConnection) throws Exception, java.lang.Exception + { + + final XInteractionHandler interAction = new CopyTableInterActionHandler(); + final XComponentContext context = getComponentContext(); + final XPropertySet sourceDescriptor = DataAccessDescriptorFactory.get(context).createDataAccessDescriptor(); + sourceDescriptor.setPropertyValue("CommandType", CommandType.TABLE); + sourceDescriptor.setPropertyValue("Command", tableName); + sourceDescriptor.setPropertyValue("ActiveConnection", sourceConnection); + + final XPropertySet destDescriptor = DataAccessDescriptorFactory.get(context).createDataAccessDescriptor(); + destDescriptor.setPropertyValue("ActiveConnection", destConnection); + + final XCopyTableWizard copyWizard = com.sun.star.sdb.application.CopyTableWizard.createWithInteractionHandler( + context, sourceDescriptor, destDescriptor, interAction); + copyWizard.setOperation((short) 0); // com.sun.star.sdb.application.CopyDefinitionAndData + Optional<String> auto = new Optional<String>(); + + auto.IsPresent = destConnection.getMetaData().supportsCoreSQLGrammar(); + if (auto.IsPresent) + { + auto.Value = "ID_test"; + } + copyWizard.setCreatePrimaryKey(auto); + Thread thread = new Thread(new CopyThread(copyWizard)); + thread.start(); + util.utils.shortWait(); + + try + { + final XWindow dialog = getActiveWindow(); + final UITools uiTools = new UITools(dialog); + final XAccessible root = uiTools.getRoot(); + final XAccessibleContext accContext = root.getAccessibleContext(); + final long count = accContext.getAccessibleChildCount(); + String buttonName = "Create"; + final XAccessibleContext childContext = accContext.getAccessibleChild(count - 3).getAccessibleContext(); + final String name = childContext.getAccessibleName(); + if (name != null && !"".equals(name)) + { + buttonName = name; + } + try + { + uiTools.clickButton(buttonName); + } + catch (java.lang.Exception exception) + { + exception.printStackTrace( System.err ); + } + } + catch (com.sun.star.lang.IndexOutOfBoundsException indexOutOfBoundsException) + { + } + util.utils.shortWait(); + + thread.join(); + } + +} diff --git a/dbaccess/qa/complex/dbaccess/DataSource.java b/dbaccess/qa/complex/dbaccess/DataSource.java new file mode 100644 index 0000000000..0413a6e07b --- /dev/null +++ b/dbaccess/qa/complex/dbaccess/DataSource.java @@ -0,0 +1,68 @@ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ +package complex.dbaccess; + +import com.sun.star.container.XNameAccess; +import com.sun.star.uno.UnoRuntime; +import com.sun.star.uno.XNamingService; +import connectivity.tools.CRMDatabase; +import connectivity.tools.HsqlDatabase; + +// ---------- junit imports ----------------- +import org.junit.Test; +import static org.junit.Assert.*; + + + +public class DataSource extends TestCase +{ + + HsqlDatabase m_database; + connectivity.tools.DataSource m_dataSource; + + + private void createTestCase() throws Exception + { + if (m_database == null) + { + final CRMDatabase database = new CRMDatabase( getMSF(), false ); + m_database = database.getDatabase(); + m_dataSource = m_database.getDataSource(); + } + } + + + @Test + public void testRegistrationName() throws Exception + { + createTestCase(); + // 1. check the existing "Bibliography" data source whether it has the proper name + String dataSourceName = "Bibliography"; + final connectivity.tools.DataSource bibliography = new connectivity.tools.DataSource(getMSF(), dataSourceName); + assertEquals("pre-registered database has a wrong name!", dataSourceName, bibliography.getName()); + // 2. register a newly created data source, and verify it has the proper name + dataSourceName = "someDataSource"; + final XNamingService dataSourceRegistrations = UnoRuntime.queryInterface( + XNamingService.class, getMSF().createInstance( "com.sun.star.sdb.DatabaseContext" ) ); + final XNameAccess existenceCheck = UnoRuntime.queryInterface( XNameAccess.class, dataSourceRegistrations ); + if ( existenceCheck.hasByName( "someDataSource" ) ) + dataSourceRegistrations.revokeObject( "someDataSource" ); + dataSourceRegistrations.registerObject("someDataSource", m_dataSource.getXDataSource()); + assertEquals("registration name of a newly registered data source is wrong", dataSourceName, m_dataSource.getName()); + } +} diff --git a/dbaccess/qa/complex/dbaccess/DatabaseApplication.java b/dbaccess/qa/complex/dbaccess/DatabaseApplication.java new file mode 100644 index 0000000000..dad7644f8b --- /dev/null +++ b/dbaccess/qa/complex/dbaccess/DatabaseApplication.java @@ -0,0 +1,73 @@ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ +package complex.dbaccess; + +import com.sun.star.beans.PropertyValue; +import com.sun.star.frame.FrameSearchFlag; +import com.sun.star.frame.XComponentLoader; +import com.sun.star.frame.XModel; +import com.sun.star.frame.XStorable; +import com.sun.star.lang.XComponent; +import com.sun.star.sdb.XOfficeDatabaseDocument; +import com.sun.star.sdb.application.XDatabaseDocumentUI; +import com.sun.star.uno.Exception; +import com.sun.star.uno.UnoRuntime; +import connectivity.tools.DatabaseAccess; + +public class DatabaseApplication +{ + + private final XOfficeDatabaseDocument databaseDocument; + private final XDatabaseDocumentUI documentUI; + + public DatabaseApplication(final DatabaseAccess _db) throws Exception + { + databaseDocument = _db.getDatabaseDocument(); + + // load it into a frame + final Object object = _db.getORB().createInstance("com.sun.star.frame.Desktop"); + final XComponentLoader xComponentLoader = UnoRuntime.queryInterface(XComponentLoader.class, object); + final XComponent loadedComponent = xComponentLoader.loadComponentFromURL(_db.getDocumentURL(), "_blank", FrameSearchFlag.ALL, new PropertyValue[0]); + + // get the controller, which provides access to various UI operations + final XModel docModel = UnoRuntime.queryInterface(XModel.class, loadedComponent); + documentUI = UnoRuntime.queryInterface(XDatabaseDocumentUI.class, docModel.getCurrentController()); + documentUI.connect(); + } + + public XDatabaseDocumentUI getDocumentUI() + { + return documentUI; + } + + public void store() + { + // store the doc in a new location + try + { + final XStorable storeDoc = UnoRuntime.queryInterface(XStorable.class, databaseDocument); + if (storeDoc != null) + { + storeDoc.store(); + } + } + catch (com.sun.star.io.IOException iOException) + { + } + } +} diff --git a/dbaccess/qa/complex/dbaccess/DatabaseDocument.java b/dbaccess/qa/complex/dbaccess/DatabaseDocument.java new file mode 100644 index 0000000000..3087ad6374 --- /dev/null +++ b/dbaccess/qa/complex/dbaccess/DatabaseDocument.java @@ -0,0 +1,996 @@ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ +package complex.dbaccess; + +import com.sun.star.configuration.theDefaultProvider; +import com.sun.star.lang.NotInitializedException; +import com.sun.star.frame.DoubleInitializationException; +import com.sun.star.awt.XTopWindow; +import com.sun.star.beans.PropertyState; +import com.sun.star.document.DocumentEvent; +import com.sun.star.lang.XEventListener; +import com.sun.star.lang.XMultiServiceFactory; +import com.sun.star.script.XStorageBasedLibraryContainer; +import com.sun.star.task.XInteractionRequest; + +import com.sun.star.uno.Type; +import com.sun.star.uno.UnoRuntime; +import com.sun.star.frame.XStorable; +import com.sun.star.beans.NamedValue; +import com.sun.star.beans.PropertyValue; +import com.sun.star.beans.XPropertySet; +import com.sun.star.container.XNameContainer; +import com.sun.star.container.XSet; +import com.sun.star.document.XDocumentEventListener; +import com.sun.star.document.XEmbeddedScripts; +import com.sun.star.document.XEventsSupplier; +import com.sun.star.lang.XComponent; +import com.sun.star.frame.XComponentLoader; +import com.sun.star.frame.XDispatch; +import com.sun.star.frame.XDispatchProvider; +import com.sun.star.frame.XFrame; +import com.sun.star.frame.XGlobalEventBroadcaster; +import com.sun.star.frame.XLoadable; +import com.sun.star.frame.XModel; +import com.sun.star.frame.XModel2; +import com.sun.star.frame.XTitle; +import com.sun.star.frame.theGlobalEventBroadcaster; +import com.sun.star.lang.EventObject; +import com.sun.star.lang.XServiceInfo; +import com.sun.star.lang.XSingleComponentFactory; +import com.sun.star.lang.XTypeProvider; +import com.sun.star.script.provider.XScriptProviderSupplier; +import com.sun.star.sdb.XDocumentDataSource; + +import com.sun.star.sdb.XFormDocumentsSupplier; +import com.sun.star.sdb.XOfficeDatabaseDocument; +import com.sun.star.sdb.XReportDocumentsSupplier; +import com.sun.star.task.DocumentMacroConfirmationRequest; +import com.sun.star.task.XInteractionApprove; +import com.sun.star.task.XInteractionContinuation; +import com.sun.star.task.XInteractionHandler; +import com.sun.star.uno.XComponentContext; +import com.sun.star.util.CloseVetoException; +import com.sun.star.util.URL; +import com.sun.star.util.XChangesBatch; +import com.sun.star.util.XCloseable; +import com.sun.star.util.XModifiable; +import com.sun.star.util.XURLTransformer; +import java.io.IOException; +import java.util.Iterator; +import java.util.ArrayList; +import java.util.logging.Level; +import java.util.logging.Logger; + +// ---------- junit imports ----------------- +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import static org.junit.Assert.*; + + +public class DatabaseDocument extends TestCase implements com.sun.star.document.XDocumentEventListener +{ + + private static final String _BLANK = "_blank"; + private XComponent m_callbackFactory = null; + private final ArrayList<String> m_documentEvents = new ArrayList<String>(); + private final ArrayList<String> m_globalEvents = new ArrayList<String>(); + // for those states, see testDocumentEvents + private static short STATE_NOT_STARTED = 0; + private static short STATE_LOADING_DOC = 1; + private static short STATE_MACRO_EXEC_APPROVED = 2; + private static short STATE_ON_LOAD_RECEIVED = 3; + private short m_loadDocState = STATE_NOT_STARTED; + + + /** a helper class which can be used by the Basic scripts in our test documents + * to notify us of events in this document + */ + private class CallbackComponent implements XDocumentEventListener, XTypeProvider + { + + public void documentEventOccured(DocumentEvent _event) + { + onDocumentEvent(_event); + } + + public void disposing(com.sun.star.lang.EventObject _Event) + { + // not interested in + } + + public Type[] getTypes() + { + final Class interfaces[] = getClass().getInterfaces(); + Type types[] = new Type[interfaces.length]; + for (int i = 0; i < interfaces.length; ++i) + { + types[i] = new Type(interfaces[i]); + } + return types; + } + + public byte[] getImplementationId() + { + return new byte[0]; + } + } + + + private static String getCallbackComponentServiceName() + { + return "org.openoffice.complex.dbaccess.EventCallback"; + } + + + /** a factory for a CallbackComponent + */ + private class CallbackComponentFactory implements XSingleComponentFactory, XServiceInfo, XComponent + { + + private final ArrayList<XEventListener> m_eventListeners = new ArrayList<XEventListener>(); + + public Object createInstanceWithContext(XComponentContext _context) throws com.sun.star.uno.Exception + { + return new CallbackComponent(); + } + + public Object createInstanceWithArgumentsAndContext(Object[] arg0, XComponentContext _context) throws com.sun.star.uno.Exception + { + return createInstanceWithContext(_context); + } + + public String getImplementationName() + { + return "org.openoffice.complex.dbaccess.CallbackComponent"; + } + + public boolean supportsService(String _service) + { + return _service.equals(getCallbackComponentServiceName()); + } + + public String[] getSupportedServiceNames() + { + return new String[] + { + getCallbackComponentServiceName() + }; + } + + @SuppressWarnings("unchecked") + public void dispose() + { + final EventObject event = new EventObject(this); + + final ArrayList<XEventListener> eventListenersCopy = (ArrayList<XEventListener>)m_eventListeners.clone(); + final Iterator<XEventListener> iter = eventListenersCopy.iterator(); + while (iter.hasNext()) + { + iter.next().disposing(event); + } + } + + public void addEventListener(XEventListener _listener) + { + if (_listener != null) + { + m_eventListeners.add(_listener); + } + } + + public void removeEventListener(XEventListener _listener) + { + m_eventListeners.remove(_listener); + } + } + + + private class MacroExecutionApprove implements XInteractionHandler + { + + private XInteractionHandler m_defaultHandler = null; + + MacroExecutionApprove(XMultiServiceFactory _factory) + { + try + { + m_defaultHandler = UnoRuntime.queryInterface(XInteractionHandler.class, _factory.createInstance("com.sun.star.task.InteractionHandler")); + } + catch (Exception ex) + { + Logger.getLogger(DatabaseDocument.class.getName()).log(Level.SEVERE, null, ex); + } + } + + public void handle(XInteractionRequest _request) + { + final Object request = _request.getRequest(); + if (!(request instanceof DocumentMacroConfirmationRequest) && (m_defaultHandler != null)) + { + m_defaultHandler.handle(_request); + return; + } + + assertEquals("interaction handler called in wrong state", STATE_LOADING_DOC, m_loadDocState); + + // auto-approve + final XInteractionContinuation continuations[] = _request.getContinuations(); + for (int i = 0; i < continuations.length; ++i) + { + final XInteractionApprove approve = UnoRuntime.queryInterface(XInteractionApprove.class, continuations[i]); + if (approve != null) + { + approve.select(); + m_loadDocState = STATE_MACRO_EXEC_APPROVED; + break; + } + } + } + } + + + + @Override + @Before + public void before() throws java.lang.Exception + { + super.before(); + + try + { + // at our service factory, insert a new factory for our CallbackComponent + // this will allow the Basic code in our test documents to call back into this test case + // here, by just instantiating this service + final XSet globalFactory = UnoRuntime.queryInterface(XSet.class, getMSF()); + m_callbackFactory = new CallbackComponentFactory(); + globalFactory.insert(m_callbackFactory); + + // register ourself as listener at the global event broadcaster + final XGlobalEventBroadcaster broadcaster + = theGlobalEventBroadcaster.get(getComponentContext()); + broadcaster.addDocumentEventListener(this); + } + catch (Exception e) + { + System.out.println("could not create the test case, error message:\n" + e.getMessage()); + e.printStackTrace(System.err); + fail("failed to create the test case"); + } + } + + + @Override + @After + public void after() throws java.lang.Exception + { + try + { + // dispose our callback factory. This will automatically remove it from our service + // factory + m_callbackFactory.dispose(); + + // revoke ourself as listener at the global event broadcaster + final XGlobalEventBroadcaster broadcaster + = theGlobalEventBroadcaster.get(getComponentContext()); + broadcaster.removeDocumentEventListener(this); + } + catch (Exception e) + { + System.out.println("could not create the test case, error message:\n" + e.getMessage()); + e.printStackTrace(System.err); + fail("failed to close the test case"); + } + + super.after(); + } + + + private static class UnoMethodDescriptor + { + + public Class unoInterfaceClass = null; + public String methodName = null; + + UnoMethodDescriptor(Class _class, String _method) + { + unoInterfaceClass = _class; + methodName = _method; + } + } + + + private void impl_checkDocumentInitState(Object _document, boolean _isInitialized) + { + // things you cannot do with an uninitialized document: + final UnoMethodDescriptor[] unsupportedMethods = new UnoMethodDescriptor[] + { + new UnoMethodDescriptor(XStorable.class, "store"), + new UnoMethodDescriptor(XFormDocumentsSupplier.class, "getFormDocuments"), + new UnoMethodDescriptor(XReportDocumentsSupplier.class, "getReportDocuments"), + new UnoMethodDescriptor(XScriptProviderSupplier.class, "getScriptProvider"), + new UnoMethodDescriptor(XEventsSupplier.class, "getEvents"), + new UnoMethodDescriptor(XTitle.class, "getTitle"), + new UnoMethodDescriptor(XModel2.class, "getControllers") + // (there's much more than this, but we cannot list all methods here, can we ...) + }; + + for (int i = 0; i < unsupportedMethods.length; ++i) + { + assureException( _document, unsupportedMethods[i].unoInterfaceClass, + unsupportedMethods[i].methodName, new Object[]{}, _isInitialized ? null : NotInitializedException.class ); + } + } + + + private XModel impl_createDocument() throws Exception + { + final XModel databaseDoc = UnoRuntime.queryInterface(XModel.class, getMSF().createInstance("com.sun.star.sdb.OfficeDatabaseDocument")); + + // should not be initialized here - we did neither initNew nor load nor storeAsURL it + impl_checkDocumentInitState(databaseDoc, false); + + return databaseDoc; + } + + + private void impl_closeDocument(XModel _databaseDoc) throws CloseVetoException, Exception + { + final XCloseable closeDoc = UnoRuntime.queryInterface(XCloseable.class, _databaseDoc); + closeDoc.close(true); + } + + + private XModel impl_createEmptyEmbeddedHSQLDocument() throws Exception, IOException + { + final XModel databaseDoc = UnoRuntime.queryInterface(XModel.class, getMSF().createInstance("com.sun.star.sdb.OfficeDatabaseDocument")); + final XStorable storeDoc = UnoRuntime.queryInterface(XStorable.class, databaseDoc); + + // verify the document rejects API calls which require it to be initialized + impl_checkDocumentInitState(databaseDoc, false); + + // though the document is not initialized, you can ask for the location, the URL, and the args + final String location = storeDoc.getLocation(); + final String url = databaseDoc.getURL(); + final PropertyValue[] args = databaseDoc.getArgs(); + // they should be all empty at this time + assertEquals("location is expected to be empty here", "", location); + assertEquals("URL is expected to be empty here", "", url); + assertEquals("Args are expected to be empty here", 0, args.length); + + // and, you should be able to set properties at the data source + final XOfficeDatabaseDocument dataSourceAccess = UnoRuntime.queryInterface(XOfficeDatabaseDocument.class, databaseDoc); + final XPropertySet dsProperties = UnoRuntime.queryInterface(XPropertySet.class, dataSourceAccess.getDataSource()); + dsProperties.setPropertyValue("URL", "sdbc:embedded:hsqldb"); + + final String documentURL = createTempFileURL(); + storeDoc.storeAsURL(documentURL, new PropertyValue[0]); + + // now that the document is stored, ... + // ... its URL should be correct + assertEquals("wrong URL after storing the document", documentURL, databaseDoc.getURL()); + // ... it should be initialized + impl_checkDocumentInitState(databaseDoc, true); + + return databaseDoc; + } + + + @Test + public void testLoadable() throws Exception, IOException + { + XModel databaseDoc = impl_createEmptyEmbeddedHSQLDocument(); + String documentURL = databaseDoc.getURL(); + + // there's three methods how you can initialize a database document: + + + // 1. XStorable::storeAsURL + // (this is for compatibility reasons, to not break existing code) + // this test is already made in impl_createEmptyEmbeddedHSQLDocument + + + // 2. XLoadable::load + databaseDoc = UnoRuntime.queryInterface(XModel.class, getMSF().createInstance("com.sun.star.sdb.OfficeDatabaseDocument")); + documentURL = copyToTempFile(documentURL); + // load the doc, and verify it's initialized then, and has the proper URL + XLoadable loadDoc = UnoRuntime.queryInterface(XLoadable.class, databaseDoc); + loadDoc.load(new PropertyValue[] + { + new PropertyValue("URL", 0, documentURL, PropertyState.DIRECT_VALUE) + }); + databaseDoc.attachResource(documentURL, new PropertyValue[0]); + + assertEquals("wrong URL after loading the document", documentURL, databaseDoc.getURL()); + impl_checkDocumentInitState(databaseDoc, true); + + // and while we are here ... initializing the same document again should not be possible + assureException( databaseDoc, XLoadable.class, "initNew", new Object[0], + DoubleInitializationException.class ); + assureException( databaseDoc, XLoadable.class, "load", new Object[] { new PropertyValue[0] }, + DoubleInitializationException.class ); + + + // 3. XLoadable::initNew + impl_closeDocument(databaseDoc); + databaseDoc = impl_createDocument(); + loadDoc = UnoRuntime.queryInterface(XLoadable.class, databaseDoc); + loadDoc.initNew(); + assertEquals("wrong URL after initializing the document", "", databaseDoc.getURL()); + impl_checkDocumentInitState(databaseDoc, true); + + // same as above - initializing the document a second time must fail + assureException( databaseDoc, XLoadable.class, "initNew", new Object[0], + DoubleInitializationException.class ); + assureException( databaseDoc, XLoadable.class, "load", new Object[] { new PropertyValue[0] }, + DoubleInitializationException.class ); + } + + + private PropertyValue[] impl_getMarkerLoadArgs() + { + return new PropertyValue[] + { + new PropertyValue( "PickListEntry", 0, false, PropertyState.DIRECT_VALUE ), + new PropertyValue( "TestCase_Marker", 0, "Yes", PropertyState.DIRECT_VALUE ) + }; + } + + + private boolean impl_hasMarker( final PropertyValue[] _args ) + { + for ( int i=0; i<_args.length; ++i ) + { + if ( _args[i].Name.equals( "TestCase_Marker" ) && _args[i].Value.equals( "Yes" ) ) + { + return true; + } + } + return false; + } + + + private PropertyValue[] impl_getDefaultLoadArgs() + { + return new PropertyValue[] + { + new PropertyValue("PickListEntry", 0, false, PropertyState.DIRECT_VALUE) + }; + } + + + private PropertyValue[] impl_getMacroExecLoadArgs() + { + return new PropertyValue[] + { + new PropertyValue("PickListEntry", 0, false, PropertyState.DIRECT_VALUE), + new PropertyValue("MacroExecutionMode", 0, com.sun.star.document.MacroExecMode.USE_CONFIG, PropertyState.DIRECT_VALUE), + new PropertyValue("InteractionHandler", 0, new MacroExecutionApprove(getMSF()), PropertyState.DIRECT_VALUE) + }; + } + + + private int impl_setMacroSecurityLevel(int _level) throws Exception + { + final XMultiServiceFactory configProvider = theDefaultProvider.get( + getComponentContext()); + + final NamedValue[] args = new NamedValue[] + { + new NamedValue("nodepath", "/org.openoffice.Office.Common/Security/Scripting") + }; + + final XPropertySet securitySettings = UnoRuntime.queryInterface(XPropertySet.class, configProvider.createInstanceWithArguments("com.sun.star.configuration.ConfigurationUpdateAccess", args)); + final int oldValue = ((Integer) securitySettings.getPropertyValue("MacroSecurityLevel")).intValue(); + securitySettings.setPropertyValue("MacroSecurityLevel", Integer.valueOf(_level)); + + final XChangesBatch committer = UnoRuntime.queryInterface(XChangesBatch.class, securitySettings); + committer.commitChanges(); + + return oldValue; + } + + + private XModel impl_loadDocument( final String _documentURL, final PropertyValue[] _loadArgs ) throws Exception + { + final XComponentLoader loader = UnoRuntime.queryInterface(XComponentLoader.class, getMSF().createInstance("com.sun.star.frame.Desktop")); + return UnoRuntime.queryInterface(XModel.class, loader.loadComponentFromURL(_documentURL, _BLANK, 0, _loadArgs)); + } + + + private void impl_storeDocument( final XModel _document ) throws Exception + { + // store the document + FileHelper.getOOoCompatibleFileURL( _document.getURL() ); + final XStorable storeDoc = UnoRuntime.queryInterface(XStorable.class, _document); + storeDoc.store(); + + } + + + private XModel impl_createDocWithMacro( final String _libName, final String _moduleName, final String _code ) throws Exception, IOException + { + // create an empty document + XModel databaseDoc = impl_createEmptyEmbeddedHSQLDocument(); + + // create Basic library/module therein + final XEmbeddedScripts embeddedScripts = UnoRuntime.queryInterface(XEmbeddedScripts.class, databaseDoc); + final XStorageBasedLibraryContainer basicLibs = embeddedScripts.getBasicLibraries(); + final XNameContainer newLib = basicLibs.createLibrary( _libName ); + newLib.insertByName( _moduleName, _code ); + + return databaseDoc; + } + + + /** Tests various aspects of database document "revenants" + * + * Well, I do not really have a good term for this... The point is, database documents are in real + * only *one* aspect of a more complex thing. The second aspect is a data source. Both, in some sense, + * just represent different views on the same thing. For a given database, there's at each time at most + * one data source, and at most one database document. Both have an independent life time, and are + * created when needed. + * In particular, a document can be closed (this is what happens when the last UI window displaying + * this document is closed), and then dies. Now when the other "view", the data source, still exists, + * the underlying document data is not discarded, but kept alive (else the data source would die + * just because the document dies, which is not desired). If the document is loaded, again, then + * it is re-created, using the data of its previous "incarnation". + * + * This method here tests some of those aspects of a document which should survive the death of one + * instance and re-creation as a revenant. + */ + @Test + public void testDocumentRevenants() throws Exception, IOException + { + // create an empty document + XModel databaseDoc = impl_createDocWithMacro( "Lib", "Module", + "Sub Hello\n" + + " MsgBox \"Hello\"\n" + + "End Sub\n" + ); + impl_storeDocument( databaseDoc ); + final String documentURL = databaseDoc.getURL(); + + // at this stage, the marker should not yet be present in the doc's args, else some of the below + // tests become meaningless + assertTrue( "A newly created doc should not have the test case marker", !impl_hasMarker( databaseDoc.getArgs() ) ); + + // obtain the DataSource associated with the document. Keeping this alive + // ensures that the "impl data" of the document is kept alive, too, so when closing + // and re-opening it, this "impl data" must be re-used. + XDocumentDataSource dataSource = UnoRuntime.queryInterface(XDocumentDataSource.class, UnoRuntime.queryInterface(XOfficeDatabaseDocument.class, databaseDoc).getDataSource()); + + // close and reload the doc + impl_closeDocument(databaseDoc); + databaseDoc = impl_loadDocument( documentURL, impl_getMarkerLoadArgs() ); + // since we just put the marker into the load-call, it should be present at the doc + assertTrue( "The test case marker got lost.", impl_hasMarker( databaseDoc.getArgs() ) ); + + // The basic library should have survived + final XEmbeddedScripts embeddedScripts = UnoRuntime.queryInterface(XEmbeddedScripts.class, databaseDoc); + final XStorageBasedLibraryContainer basicLibs = embeddedScripts.getBasicLibraries(); + assertTrue( "Basic lib did not survive reloading a closed document", basicLibs.hasByName( "Lib" ) ); + final XNameContainer lib = UnoRuntime.queryInterface(XNameContainer.class, basicLibs.getByName("Lib")); + assertTrue( "Basic module did not survive reloading a closed document", lib.hasByName( "Module" ) ); + + // now closing the doc, and obtaining it from the data source, should preserve the marker we put into the load + // args + impl_closeDocument( databaseDoc ); + databaseDoc = UnoRuntime.queryInterface(XModel.class, dataSource.getDatabaseDocument()); + assertTrue( "The test case marker did not survive re-retrieval of the doc from the data source.", + impl_hasMarker( databaseDoc.getArgs() ) ); + + // on the other hand, closing and regularly re-loading the doc *without* the marker should indeed + // lose it + impl_closeDocument( databaseDoc ); + databaseDoc = impl_loadDocument( documentURL, impl_getDefaultLoadArgs() ); + assertTrue( "Reloading the document kept the old args, instead of the newly supplied ones.", + !impl_hasMarker( databaseDoc.getArgs() ) ); + + // clean up + impl_closeDocument( databaseDoc ); + } + + + @Test + public void testDocumentEvents() throws Exception, IOException + { + // create an empty document + final String libName = "EventHandlers"; + final String moduleName = "all"; + final String eventHandlerCode = + "Option Explicit\n" + + "\n" + + "Sub OnLoad\n" + + " Dim oCallback as Object\n" + + " oCallback = createUnoService( \"" + getCallbackComponentServiceName() + "\" )\n" + + "\n" + + " ' as long as the Document is not passed to the Basic callbacks, we need to create\n" + + " ' one ourself\n" + + " Dim oEvent as new com.sun.star.document.DocumentEvent\n" + + " oEvent.EventName = \"OnLoad\"\n" + + " oEvent.Source = ThisComponent\n" + + "\n" + + " oCallback.documentEventOccurred( oEvent )\n" + + "End Sub\n"; + XModel databaseDoc = impl_createDocWithMacro( libName, moduleName, eventHandlerCode ); + final String documentURL = databaseDoc.getURL(); + + // bind the macro to the OnLoad event + final String macroURI = "vnd.sun.star.script:" + libName + "." + moduleName + ".OnLoad?language=Basic&location=document"; + final XEventsSupplier eventsSupplier = UnoRuntime.queryInterface(XEventsSupplier.class, databaseDoc); + eventsSupplier.getEvents().replaceByName("OnLoad", new PropertyValue[] + { + new PropertyValue("EventType", 0, "Script", PropertyState.DIRECT_VALUE), + new PropertyValue("Script", 0, macroURI, PropertyState.DIRECT_VALUE) + }); + + // store the document, and close it + impl_storeDocument( databaseDoc ); + impl_closeDocument( databaseDoc ); + + // ensure the macro security configuration is "ask the user for document macro execution" + final int oldSecurityLevel = impl_setMacroSecurityLevel(1); + + // load it, again + m_loadDocState = STATE_LOADING_DOC; + // expected order of states is: + // STATE_LOADING_DOC - initialized here + // STATE_MACRO_EXEC_APPROVED - done in our interaction handler, which auto-approves the execution of macros + // STATE_ON_LOAD_RECEIVED - done in our callback for the document events + // + // In particular, it is important that the interaction handler (which plays the role of the user confirmation + // here) is called before the OnLoad notification is received - since the latter happens from within + // a Basic macro which is bound to the OnLoad event of the document. + + final String context = "OnLoad"; + impl_startObservingEvents(context); + databaseDoc = impl_loadDocument( documentURL, impl_getMacroExecLoadArgs() ); + impl_stopObservingEvents(m_documentEvents, new String[] + { + "OnLoad" + }, context); + + assertEquals("our provided interaction handler was not called", STATE_ON_LOAD_RECEIVED, m_loadDocState); + + // restore macro security level + impl_setMacroSecurityLevel(oldSecurityLevel); + + // close the document + impl_closeDocument(databaseDoc); + } + + + @Test + public void testGlobalEvents() throws Exception, IOException + { + XModel databaseDoc = impl_createEmptyEmbeddedHSQLDocument(); + final XStorable storeDoc = UnoRuntime.queryInterface(XStorable.class, databaseDoc); + + String context, newURL; + + // XStorable.store + final String oldURL = databaseDoc.getURL(); + context = "store"; + impl_startObservingEvents(context); + storeDoc.store(); + assertEquals("store is not expected to change the document URL", databaseDoc.getURL(), oldURL); + impl_stopObservingEvents(m_globalEvents, new String[] + { + "OnSave", "OnSaveDone" + }, context); + + // XStorable.storeToURL + context = "storeToURL"; + impl_startObservingEvents(context); + storeDoc.storeToURL(createTempFileURL(), new PropertyValue[0]); + assertEquals("storetoURL is not expected to change the document URL", databaseDoc.getURL(), oldURL); + impl_stopObservingEvents(m_globalEvents, new String[] + { + "OnSaveTo", "OnSaveToDone" + }, context); + + // XStorable.storeAsURL + newURL = createTempFileURL(); + context = "storeAsURL"; + impl_startObservingEvents(context); + storeDoc.storeAsURL(newURL, new PropertyValue[0]); + assertEquals("storeAsURL is expected to change the document URL", databaseDoc.getURL(), newURL); + impl_stopObservingEvents(m_globalEvents, new String[] + { + "OnSaveAs", "OnSaveAsDone" + }, context); + + // XModifiable.setModified + final XModifiable modifyDoc = UnoRuntime.queryInterface(XModifiable.class, databaseDoc); + context = "setModified"; + impl_startObservingEvents(context); + modifyDoc.setModified(true); + assertEquals("setModified didn't work", modifyDoc.isModified(), true); + impl_stopObservingEvents(m_globalEvents, new String[] + { + "OnModifyChanged" + }, context); + + // XStorable.store, with implicit reset of the "Modified" flag + context = "store (2)"; + impl_startObservingEvents(context); + storeDoc.store(); + assertEquals("'store' should implicitly reset the modified flag", modifyDoc.isModified(), false); + impl_stopObservingEvents(m_globalEvents, new String[] + { + "OnSave", "OnSaveDone", "OnModifyChanged" + }, context); + + // XComponentLoader.loadComponentFromURL + newURL = copyToTempFile(databaseDoc.getURL()); + final XComponentLoader loader = UnoRuntime.queryInterface(XComponentLoader.class, getMSF().createInstance("com.sun.star.frame.Desktop")); + context = "loadComponentFromURL"; + impl_startObservingEvents(context); + databaseDoc = UnoRuntime.queryInterface(XModel.class, loader.loadComponentFromURL(newURL, _BLANK, 0, impl_getDefaultLoadArgs())); + impl_stopObservingEvents(m_globalEvents, + new String[] + { + "OnLoadFinished", "OnViewCreated", "OnFocus", "OnLoad" + }, context); + + // closing a document by API + final XCloseable closeDoc = UnoRuntime.queryInterface(XCloseable.class, databaseDoc); + context = "close (API)"; + impl_startObservingEvents(context); + closeDoc.close(true); + impl_stopObservingEvents(m_globalEvents, + new String[] + { + "OnPrepareUnload", "OnViewClosed", "OnUnload" + }, context); + + // closing a document via UI + context = "close (UI)"; + impl_startObservingEvents("prepare for '" + context + "'"); + databaseDoc = UnoRuntime.queryInterface(XModel.class, loader.loadComponentFromURL(newURL, _BLANK, 0, impl_getDefaultLoadArgs())); + impl_waitForEvent(m_globalEvents, "OnLoad", 5000); + // wait for all events to arrive - OnLoad should be the last one + + final XDispatchProvider dispatchProvider = UnoRuntime.queryInterface(XDispatchProvider.class, databaseDoc.getCurrentController().getFrame()); + final URL url = impl_getURL(".uno:CloseDoc"); + final XDispatch dispatcher = dispatchProvider.queryDispatch(url, "", 0); + impl_startObservingEvents(context); + dispatcher.dispatch(url, new PropertyValue[0]); + impl_stopObservingEvents(m_globalEvents, + new String[] + { + "OnPrepareViewClosing", "OnViewClosed", "OnPrepareUnload", "OnUnload" + }, context); + + // creating a new document + databaseDoc = impl_createDocument(); + final XLoadable loadDoc = UnoRuntime.queryInterface(XLoadable.class, databaseDoc); + context = "initNew"; + impl_startObservingEvents(context); + loadDoc.initNew(); + impl_stopObservingEvents(m_globalEvents, new String[] + { + "OnCreate" + }, context); + + impl_startObservingEvents(context + " (cleanup)"); + impl_closeDocument(databaseDoc); + impl_waitForEvent(m_globalEvents, "OnUnload", 5000); + + // focus changes + context = "activation"; + // for this, load a database document ... + impl_startObservingEvents("prepare for '" + context + "'"); + databaseDoc = UnoRuntime.queryInterface(XModel.class, loader.loadComponentFromURL(newURL, _BLANK, 0, impl_getDefaultLoadArgs())); + final int previousOnLoadEventPos = impl_waitForEvent(m_globalEvents, "OnLoad", 5000); + // ... and another document ... + final String otherURL = copyToTempFile(databaseDoc.getURL()); + final XModel otherDoc = UnoRuntime.queryInterface(XModel.class, loader.loadComponentFromURL(otherURL, _BLANK, 0, impl_getDefaultLoadArgs())); + impl_raise(otherDoc); + impl_waitForEvent(m_globalEvents, "OnLoad", 5000, previousOnLoadEventPos + 1); + + // ... and switch between the two + impl_startObservingEvents(context); + impl_raise(databaseDoc); + impl_stopObservingEvents(m_globalEvents, new String[] + { + "OnUnfocus", "OnFocus" + }, context); + + // cleanup + impl_startObservingEvents("cleanup after '" + context + "'"); + impl_closeDocument(databaseDoc); + impl_closeDocument(otherDoc); + } + + + private URL impl_getURL(String _completeURL) throws Exception + { + final URL[] url = + { + new URL() + }; + url[0].Complete = _completeURL; + final XURLTransformer urlTransformer = UnoRuntime.queryInterface(XURLTransformer.class, getMSF().createInstance("com.sun.star.util.URLTransformer")); + urlTransformer.parseStrict(url); + return url[0]; + } + + + private void impl_raise(XModel _document) + { + final XFrame frame = _document.getCurrentController().getFrame(); + final XTopWindow topWindow = UnoRuntime.queryInterface(XTopWindow.class, frame.getContainerWindow()); + topWindow.toFront(); + } + + + private void impl_startObservingEvents(String _context) + { + System.out.println(" " + _context + " {"); + synchronized (m_documentEvents) + { + m_documentEvents.clear(); + } + synchronized (m_globalEvents) + { + m_globalEvents.clear(); + } + } + + + private void impl_stopObservingEvents(ArrayList<String> _actualEvents, String[] _expectedEvents, String _context) + { + try + { + synchronized (_actualEvents) + { + int actualEventCount = _actualEvents.size(); + while (actualEventCount < _expectedEvents.length) + { + // well, it's possible not all events already arrived, yet - finally, some of them + // are notified asynchronously + // So, wait a few seconds. + try + { + _actualEvents.wait(20000); + } + catch (InterruptedException ex) + { + } + + if (actualEventCount == _actualEvents.size()) + // the above wait was left because of the timeout, *not* because an event + // arrived. Okay, we won't wait any longer, this is a failure. + { + break; + } + actualEventCount = _actualEvents.size(); + } + + assertEquals("wrong event count for '" + _context + "'", + _expectedEvents.length, _actualEvents.size()); + + for (int i = 0; i < _expectedEvents.length; ++i) + { + assertEquals("wrong event at position " + (i + 1) + " for '" + _context + "'", + _expectedEvents[i], _actualEvents.get(i)); + } + } + } + finally + { + System.out.println(" }"); + } + } + + + private int impl_waitForEvent(ArrayList<String> _eventQueue, String _expectedEvent, int _maxMilliseconds) + { + return impl_waitForEvent(_eventQueue, _expectedEvent, _maxMilliseconds, 0); + } + + + private int impl_waitForEvent(ArrayList<String> _eventQueue, String _expectedEvent, int _maxMilliseconds, int _firstQueueElementToCheck) + { + synchronized (_eventQueue) + { + int waitedMilliseconds = 0; + + while (waitedMilliseconds < _maxMilliseconds) + { + for (int i = _firstQueueElementToCheck; i < _eventQueue.size(); ++i) + { + if (_expectedEvent.equals(_eventQueue.get(i))) + // found the event in the queue + { + return i; + } + } + + // wait a little, perhaps the event will still arrive + try + { + _eventQueue.wait(500); + waitedMilliseconds += 500; + } + catch (InterruptedException e) + { + } + } + } + + fail("expected event '" + _expectedEvent + "' did not arrive after " + _maxMilliseconds + " milliseconds"); + return -1; + } + + + private void onDocumentEvent(DocumentEvent _Event) + { + if ("OnTitleChanged".equals(_Event.EventName)) + // OnTitleChanged events are notified too often. This is known, and accepted. + // (the deeper reason is that it's difficult to determine, in the DatabaseDocument implementation, + // when the title actually changed. In particular, when we do a saveAsURL, and then ask for a + // title *before* the TitleHelper got the document's OnSaveAsDone event, then the wrong (old) + // title is obtained. + { + return; + } + + if ((_Event.EventName.equals("OnLoad")) && (m_loadDocState != STATE_NOT_STARTED)) + { + assertEquals("OnLoad event must come *after* invocation of the interaction handler / user!", + m_loadDocState, STATE_MACRO_EXEC_APPROVED); + m_loadDocState = STATE_ON_LOAD_RECEIVED; + } + + synchronized (m_documentEvents) + { + m_documentEvents.add(_Event.EventName); + m_documentEvents.notifyAll(); + } + + System.out.println(" document event: " + _Event.EventName); + } + + + public void documentEventOccured(DocumentEvent _Event) + { + if ("OnTitleChanged".equals(_Event.EventName)) + // ignore. See onDocumentEvent for a justification + { + return; + } + + synchronized (m_globalEvents) + { + m_globalEvents.add(_Event.EventName); + m_globalEvents.notifyAll(); + } + + System.out.println(" global event: " + _Event.EventName); + } + + + public void disposing(EventObject _Event) + { + // not interested in + } +} diff --git a/dbaccess/qa/complex/dbaccess/FileHelper.java b/dbaccess/qa/complex/dbaccess/FileHelper.java new file mode 100644 index 0000000000..cae67af008 --- /dev/null +++ b/dbaccess/qa/complex/dbaccess/FileHelper.java @@ -0,0 +1,35 @@ +/* + * 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; + +public class FileHelper +{ + private FileHelper(){} + public static String getOOoCompatibleFileURL( String _javaFileURL ) + { + String returnURL = _javaFileURL; + if ( ( returnURL.indexOf( "file:/" ) == 0 ) && ( returnURL.indexOf( "file:///" ) == -1 ) ) + { + // for some reason, the URLs here in Java start with "file:/" only, instead of "file:///" + // Some of the office code doesn't like this ... + returnURL = "file:///" + returnURL.substring( 6 ); + } + return returnURL; + } +} diff --git a/dbaccess/qa/complex/dbaccess/Parser.java b/dbaccess/qa/complex/dbaccess/Parser.java new file mode 100644 index 0000000000..8025e1d379 --- /dev/null +++ b/dbaccess/qa/complex/dbaccess/Parser.java @@ -0,0 +1,183 @@ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ +package complex.dbaccess; + +import com.sun.star.beans.XPropertySet; +import com.sun.star.container.XIndexAccess; +import com.sun.star.sdb.XParametersSupplier; +import com.sun.star.sdb.XSingleSelectQueryComposer; +import com.sun.star.sdbc.DataType; +import com.sun.star.sdbc.SQLException; +import com.sun.star.uno.UnoRuntime; + + +// ---------- junit imports ----------------- +import org.junit.Test; +import static org.junit.Assert.*; + + +public class Parser extends CRMBasedTestCase +{ + + @Override + protected void createTestCase() throws Exception + { + super.createTestCase(); + m_database.getDatabase().getDataSource().createQuery( "query products", "SELECT * FROM \"products\"" ); + } + + + @Test + public void checkWhere() throws Exception + { + final XSingleSelectQueryComposer composer = createQueryComposer(); + final String SELECT = "SELECT \"products\".\"Name\" FROM \"products\" WHERE "; + final String[] queries = new String[] + { + "\"ID\" in ( 1,2,3,4)" + ,"not ( \"ID\" in ( 1,2,3,4))" + ,"(1 = 1) is true" + ,"(1 = 1) is not false" + ,"(1 = 1) is not null" + ,"not ( (1 = 1) is not null)" + ,"'a' like 'a%'" + ,"not ( 'a' like 'a%')" + ,"'a' not like 'a%'" + ,"1 between 0 and 2" + ,"not ( 1 between 0 and 2 )" + ,"1 not between 3 and 4" + ,"1 not between ( select \"ID\" from \"categories\") and ( select \"ID\" from \"categories\")" + ,"1 = 1" + ,"0 < 1" + ,"not(0 < 1)" + ,"1 > 0" + ,"not(1 > 0)" + ,"1 <> 0" + ,"(1 <> 0 and 'a' = 'a' and 'c' = 'd') or (1 = 1 and 2 = 2 and 3 = 4)" + ,"not ( 1 <> 0 )" + ,"\"CategoryID\" in ( select \"ID\" from \"categories\")" + ,"not (\"CategoryID\" in ( select \"ID\" from \"categories\"))" + ,"\"CategoryID\" not in ( select \"ID\" from \"categories\")" + }; + for (int i = 0; i < queries.length; i++) + { + composer.setQuery( SELECT + queries[i]); + } + } + + /** verifies that aliases for inner queries work as expected + */ + @Test + public void checkJoinSyntax() throws Exception + { + final XSingleSelectQueryComposer composer = createQueryComposer(); + + // feed the composer with some statements. If any of those cannot be parsed, the composer + // will throw an exception - which is a regression then + composer.setQuery( + "SELECT \"categories\".\"Name\", " + + "\"products\".\"Name\" " + + "FROM \"products\" RIGHT OUTER JOIN \"categories\" AS \"categories\" ON \"products\".\"CategoryID\" = \"categories\".\"ID\"" ); + + composer.setQuery( + "SELECT \"categories\".\"Name\", " + + "\"products\".\"Name\" " + + "FROM \"products\" LEFT OUTER JOIN \"categories\" AS \"categories\" ON \"products\".\"CategoryID\" = \"categories\".\"ID\"" ); + + composer.setQuery( + "SELECT \"categories\".\"Name\", " + + "\"products\".\"Name\" " + + "FROM \"products\" CROSS JOIN \"categories\" AS \"categories\"" ); + + composer.setQuery( + "SELECT \"categories\".\"Name\", " + + "\"products\".\"Name\" " + + "FROM \"products\" INNER JOIN \"categories\" AS \"categories\" ON \"products\".\"CategoryID\" = \"categories\".\"ID\"" ); + + // just to be sure the composer *really* parses upon setting the query: feed it with + // an unparseable statement + boolean caughtExpected = false; + try + { + composer.setQuery( "NONSENSE" ); + } + catch( SQLException e ) + { + caughtExpected = true; + } + assertTrue( "pre-condition not met: parser should except on unparseable statements, else the complete" + + "test is bogus!", caughtExpected ); + } + + + private void impl_checkParameters( final String _statement, final String[] _expectedParameterNames, final int[] _expectedParameterTypes,final String _context ) throws Exception + { + final XSingleSelectQueryComposer composer = createQueryComposer(); + composer.setQuery( _statement ); + + assertEquals( "checkParameterTypes: internal error", _expectedParameterNames.length, _expectedParameterTypes.length ); + + final XParametersSupplier paramSupp = UnoRuntime.queryInterface(XParametersSupplier.class, composer); + final XIndexAccess parameters = paramSupp.getParameters(); + + assertEquals( "(ctx: " + _context + ") unexpected parameter count", _expectedParameterNames.length, parameters.getCount() ); + for ( int i=0; i<parameters.getCount(); ++i ) + { + final XPropertySet parameter = UnoRuntime.queryInterface(XPropertySet.class, parameters.getByIndex(i)); + + final String name = (String)parameter.getPropertyValue( "Name" ); + assertEquals( "(ctx: " + _context + ") unexpected parameter name for parameter number " + ( i + 1 ), _expectedParameterNames[i], name ); + + final int type = ((Integer)parameter.getPropertyValue( "Type" )).intValue(); + assertEquals( "(ctx: " + _context + ") unexpected data type for parameter number " + ( i + 1 ), _expectedParameterTypes[i], type ); + } + } + + + /** verifies that the parser properly recognizes the types of parameters + */ + @Test + public void checkParameterTypes() throws Exception + { + impl_checkParameters( + "SELECT * FROM \"all orders\" " + + "WHERE ( \"Order Date\" >= :order_date ) " + + " AND ( ( \"Customer Name\" LIKE :customer ) " + + " OR ( \"Product Name\" LIKE ? ) " + + " )", + new String[] { "order_date", "customer", "Product Name" }, + new int[] { DataType.DATE, DataType.VARCHAR, DataType.VARCHAR }, + ">= && LIKE" + ); + + impl_checkParameters( + "SELECT * FROM \"categories\" " + + "WHERE \"ID\" BETWEEN :id_lo AND :id_hi", + new String[] { "id_lo", "id_hi" }, + new int[] { DataType.INTEGER, DataType.INTEGER }, + "BETWEEN" + ); + + impl_checkParameters( + "SELECT CONCAT( :prefix, CONCAT( \"Name\", :suffix ) ) FROM \"customers\"", + new String[] { "prefix", "suffix" }, + new int[] { DataType.VARCHAR, DataType.VARCHAR }, + "CONCAT" + ); + } +} diff --git a/dbaccess/qa/complex/dbaccess/PropertyBag.java b/dbaccess/qa/complex/dbaccess/PropertyBag.java new file mode 100644 index 0000000000..6807569ec5 --- /dev/null +++ b/dbaccess/qa/complex/dbaccess/PropertyBag.java @@ -0,0 +1,302 @@ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +package complex.dbaccess; + +import com.sun.star.beans.NamedValue; +import com.sun.star.beans.PropertyState; +import com.sun.star.beans.PropertyValue; +import com.sun.star.beans.PropertyAttribute; +import com.sun.star.beans.XPropertyAccess; +import com.sun.star.beans.XPropertySet; +import com.sun.star.beans.XPropertyContainer; +import com.sun.star.uno.UnoRuntime; +import com.sun.star.lang.XMultiServiceFactory; + +// ---------- junit imports ----------------- +import org.junit.Before; +import org.junit.Test; +import static org.junit.Assert.*; + + +public class PropertyBag extends TestCase +{ + private static final String VALUE = "Value"; + private XPropertyContainer m_bag; + private XPropertySet m_set; + private XPropertyAccess m_access; + private XMultiServiceFactory m_orb = null; + + public String getTestObjectName() + { + return "PropertyBag"; + } + + @Before + @Override + public void before() + { + m_orb = getMSF(); + } + + @Test + public void checkBasics() throws Exception + { + createEmptyBag(); + System.out.println("testing the basics"); + + // check whether empty property names are rejected + boolean caughtExpected = false; + try + { + m_bag.addProperty( "", PropertyAttribute.BOUND, Integer.valueOf( 3 ) ); + } + catch(com.sun.star.lang.IllegalArgumentException e) { caughtExpected = true; } + catch(com.sun.star.uno.Exception e) { } + if ( !caughtExpected ) + { + fail("empty property names are not rejected by XPropertyContainer::addProperty"); + } + + // check whether duplicate insertions are rejected + caughtExpected = false; + try + { + m_bag.addProperty( VALUE, PropertyAttribute.BOUND, "" ); + m_bag.addProperty( VALUE, PropertyAttribute.BOUND, "" ); + } + catch(com.sun.star.beans.PropertyExistException e) { caughtExpected = true; } + catch(com.sun.star.uno.Exception e) { } + if ( !caughtExpected ) + { + fail("insertion of duplicate property names is not rejected"); + } + + // try removing the property we just added - this should fail, as it does not have + // the REMOVABLE attribute + caughtExpected = false; + try + { + m_bag.removeProperty( VALUE); + } + catch(com.sun.star.beans.NotRemoveableException e) { caughtExpected = true; } + catch(com.sun.star.uno.Exception e) { } + if ( !caughtExpected ) + { + fail("removing non-removable properties is expected to fail - but it didn't"); + } + + // try removing a non-existent property + caughtExpected = false; + try + { + m_bag.removeProperty( "NonExistent" ); + } + catch(com.sun.star.beans.UnknownPropertyException e) { caughtExpected = true; } + catch(com.sun.star.uno.Exception e) { } + if ( !caughtExpected ) + { + fail("removing non-existent properties is expected to fail - but it didn't"); + } + + // try writing and reading a value for the one property we have so far + final String testValue = "someArbitraryValue"; + m_set.setPropertyValue( VALUE , testValue); + final String currentValue = (String)m_set.getPropertyValue( VALUE); + if ( !currentValue.equals( testValue ) ) + { + fail("set property is not remembered"); + } + + // try setting an illegal value for the property + caughtExpected = false; + try + { + m_set.setPropertyValue( VALUE, Integer.valueOf( 3 ) ); + } + catch(com.sun.star.lang.IllegalArgumentException e) { caughtExpected = true; } + catch(com.sun.star.uno.Exception e) { } + if ( !caughtExpected ) + { + fail("the bag does not respect the property type we declared for the property"); + } + } + + @Test + public void checkSequenceAccess() throws com.sun.star.uno.Exception + { + System.out.println( "checking PropertySetAccess via sequences" ); + createStandardBag( false ); + + + // XPropertyAccess.setPropertyValues + final PropertyValue expectedValues[] = + { + new PropertyValue( "BoolValue", -1, Boolean.FALSE, PropertyState.DIRECT_VALUE ), + new PropertyValue( "StringValue", -1, "some text", PropertyState.DIRECT_VALUE ), + new PropertyValue( "IntegerValue", -1, Integer.valueOf( 3 ), PropertyState.DIRECT_VALUE ), + new PropertyValue( "InterfaceValue", -1, m_bag, PropertyState.DIRECT_VALUE ) + }; + m_access.setPropertyValues( expectedValues ); + + for ( int i=0; i<expectedValues.length; ++i ) + { + final Object value = m_set.getPropertyValue( expectedValues[i].Name ); + if ( !value.equals( expectedValues[i].Value ) ) + { + System.out.println( "property name : " + expectedValues[i].Name ); + System.out.println( "expected value: " + expectedValues[i].Value.toString() ); + System.out.println( "current value : " + value.toString() ); + fail( "retrieving a previously set property (" + expectedValues[i].Value.getClass().toString() + ") failed" ); + } + } + + + // XPropertyAccess.getPropertyValues + final PropertyValue currentValues[] = m_access.getPropertyValues(); + for ( int i=0; i<currentValues.length; ++i ) + { + final String name = currentValues[i].Name; + final Object value = currentValues[i].Value; + for ( int j=0; j<expectedValues.length; ++j ) + { + if ( expectedValues[j].Name.equals( name ) ) + { + if ( !expectedValues[j].Value.equals( value ) ) + { + System.out.println( "property name : " + expectedValues[j].Name ); + System.out.println( "expected value: " + expectedValues[j].Value.toString() ); + System.out.println( "current value : " + value.toString() ); + fail( "getPropertyValues failed for property '" + name + "' failed" ); + } + break; + } + } + + if ( !m_set.getPropertyValue( name ).equals( value ) ) + { + fail("XPropertyAccess::getPropertyValues() and XPropertyset::getPropertyValue results are inconsistent"); + } + } + } + + @Test + public void checkDynamicSet() + { + System.out.println( "checking proper dynamic of the set" ); + createStandardBag( false ); + + final PropertyValue props[] = + { + new PropertyValue( "BoolValue", -1, Boolean.FALSE, PropertyState.DIRECT_VALUE), + new PropertyValue( "StringValue", -1, "test", PropertyState.DIRECT_VALUE ), + new PropertyValue( "SomeOtherStringValue", -1, "string value", PropertyState.DIRECT_VALUE ) + }; + + // try setting some property values which are not existent + boolean caughtExpected = false; + try + { + m_access.setPropertyValues( props ); + } + catch( com.sun.star.beans.UnknownPropertyException e ) { caughtExpected = true; } + catch( com.sun.star.uno.Exception e ) { } + if ( !caughtExpected ) + { + fail("the set shouldn't accept unknown property values, if not explicitly told to do so"); + } + + // re-create the bag, this time allow it to implicitly add properties + createStandardBag( true ); + boolean success = false; + try { m_access.setPropertyValues( props ); success = true; } + catch( com.sun.star.uno.Exception e ) { } + if ( !success ) + { + fail("property bag failed to implicitly add unknown properties"); + } + + // see whether this property was really added, and not just ignored + final PropertyValue newlyAdded = props[ props.length - 1 ]; + try + { + if ( !m_set.getPropertyValue( newlyAdded.Name ).equals( newlyAdded.Value ) ) + { + fail("the new property was not really added, or not added with the proper value"); + } + } + catch( com.sun.star.uno.Exception e ) { } + } + + private void createEmptyBag() + { + try + { + m_bag = null; + final String serviceName = "com.sun.star.beans.PropertyBag"; + m_bag = UnoRuntime.queryInterface(XPropertyContainer.class, m_orb.createInstance(serviceName)); + if ( m_bag == null ) + { + fail("could not create a " + serviceName + " instance"); + } + m_set = UnoRuntime.queryInterface(XPropertySet.class, m_bag); + m_access = UnoRuntime.queryInterface(XPropertyAccess.class, m_bag); + } + catch( com.sun.star.uno.Exception e ) + { + } + } + + private void createStandardBag( boolean allowLazyAdding ) + { + try + { + m_bag = null; + + final Object initArgs[] = { new NamedValue( "AutomaticAddition", Boolean.valueOf( allowLazyAdding ) ) }; + + final String serviceName = "com.sun.star.beans.PropertyBag"; + m_bag = UnoRuntime.queryInterface(XPropertyContainer.class, m_orb.createInstanceWithArguments(serviceName, initArgs)); + if ( m_bag == null ) + { + fail("could not create a " + serviceName + " instance"); + } + m_set = UnoRuntime.queryInterface(XPropertySet.class, m_bag); + m_access = UnoRuntime.queryInterface(XPropertyAccess.class, m_bag); + + final Object properties[][] = + { + { "BoolValue", Boolean.TRUE }, + { "StringValue", "" }, + { "IntegerValue", Integer.valueOf( 3 ) }, + { "InterfaceValue", m_bag } + }; + for ( int i=0; i<properties.length; ++i ) + { + m_bag.addProperty( + (String)properties[i][0], + PropertyAttribute.MAYBEVOID, + properties[i][1] + ); + } + } + catch( com.sun.star.uno.Exception e ) + { + } + } +} diff --git a/dbaccess/qa/complex/dbaccess/Query.java b/dbaccess/qa/complex/dbaccess/Query.java new file mode 100644 index 0000000000..8881bb3ede --- /dev/null +++ b/dbaccess/qa/complex/dbaccess/Query.java @@ -0,0 +1,107 @@ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ +package complex.dbaccess; + +import com.sun.star.beans.XPropertySet; +import com.sun.star.container.XIndexAccess; +import com.sun.star.container.XNameAccess; +import com.sun.star.container.XNamed; +import com.sun.star.sdb.XQueriesSupplier; +import com.sun.star.sdbcx.XColumnsSupplier; +import com.sun.star.uno.UnoRuntime; +import connectivity.tools.CRMDatabase; + +// ---------- junit imports ----------------- +import org.junit.Test; +import static org.junit.Assert.*; + + +public class Query extends TestCase +{ + + connectivity.tools.HsqlDatabase m_database; + + + private void createTestCase() + { + try + { + if (m_database == null) + { + final CRMDatabase database = new CRMDatabase(getMSF(), false); + m_database = database.getDatabase(); + } + } + catch (Exception e) + { + System.out.println("could not create the test case, error message:\n" + e.getMessage()); + e.printStackTrace(System.err); + fail("failed to created the test case"); + } + } + + + @Test + public void testQueryColumns() + { + createTestCase(); + + try + { + final XQueriesSupplier suppQueries = UnoRuntime.queryInterface( + XQueriesSupplier.class, m_database.defaultConnection().getXConnection() ); + final XNameAccess queries = suppQueries.getQueries(); + + final String[] queryNames = new String[] { "parseable", "parseable native", "unparseable" }; + final String[][] expectedColumnNames = new String[][] { + new String[] { "ID", "Name", "Address", "City", "Postal","Comment" }, + new String[] { "TABLE_CATALOG", "TABLE_SCHEMA", "TABLE_NAME", "VIEW_DEFINITION", "CHECK_OPTION", "IS_UPDATABLE", "VALID" }, + new String[] { "ID_VARCHAR" } + }; + + for ( int i = 0; i < queryNames.length; ++i ) + { + if (queries.hasByName(queryNames[i])) + { + final XPropertySet query = UnoRuntime.queryInterface( + XPropertySet.class, queries.getByName( queryNames[i] ) ); + + final XColumnsSupplier suppCols = UnoRuntime.queryInterface( + XColumnsSupplier.class, query); + final XIndexAccess columns = UnoRuntime.queryInterface( + XIndexAccess.class, suppCols.getColumns()); + + // check whether the columns supplied by the query match what we expected + assertTrue( "invalid column count (found " + columns.getCount() + ", expected: " + expectedColumnNames[i].length + ") for query \"" + queryNames[i] + "\"", + columns.getCount() == expectedColumnNames[i].length ); + for ( int col = 0; col < columns.getCount(); ++col ) + { + final XNamed columnName = UnoRuntime.queryInterface( + XNamed.class, columns.getByIndex(col) ); + assertTrue( "column no. " + col + " of query \"" + queryNames[i] + "\" not matching", + columnName.getName().equals( expectedColumnNames[i][col] ) ); + } + } + } + } + catch ( Exception e ) + { + fail( "caught an unexpected exception: " + e.getMessage() ); + } + } +} diff --git a/dbaccess/qa/complex/dbaccess/QueryInQuery.java b/dbaccess/qa/complex/dbaccess/QueryInQuery.java new file mode 100644 index 0000000000..4e6a9d0ffe --- /dev/null +++ b/dbaccess/qa/complex/dbaccess/QueryInQuery.java @@ -0,0 +1,177 @@ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ +package complex.dbaccess; + +import com.sun.star.container.ElementExistException; +import com.sun.star.lang.IllegalArgumentException; +import com.sun.star.lang.WrappedTargetException; +import com.sun.star.sdb.CommandType; +import com.sun.star.sdbc.SQLException; +import connectivity.tools.HsqlColumnDescriptor; +import connectivity.tools.HsqlTableDescriptor; +import connectivity.tools.RowSet; +import com.sun.star.sdbc.XStatement; +import com.sun.star.sdbc.XResultSet; + +// ---------- junit imports ----------------- +import org.junit.Test; +import static org.junit.Assert.*; + + +public class QueryInQuery extends CRMBasedTestCase +{ + private static final String QUERY_PRODUCTS = "query products"; + + + @Override + protected void createTestCase() + { + try + { + super.createTestCase(); + m_database.getDatabase().getDataSource().createQuery( QUERY_PRODUCTS,"SELECT * FROM \"products\""); + } + catch ( Exception e ) + { + e.printStackTrace( System.err ); + fail( "caught an exception (" + e.getMessage() + ") while creating the test case" ); + } + } + + + private void verifyEqualRowSetContent( int _outerCommandType, String _outerCommand, int _innerCommandType, String _innerCommand ) throws SQLException + { + final RowSet outerRowSet = m_database.getDatabase().createRowSet( _outerCommandType, _outerCommand ); + outerRowSet.execute(); + + final RowSet innerRowSet = m_database.getDatabase().createRowSet( _innerCommandType, _innerCommand ); + innerRowSet.execute(); + + outerRowSet.last(); + innerRowSet.last(); + assertTrue( "wrong record counts", outerRowSet.getRow() == innerRowSet.getRow() ); + + outerRowSet.beforeFirst(); + innerRowSet.beforeFirst(); + assertTrue( "wrong column counts", outerRowSet.getColumnCount() == innerRowSet.getColumnCount() ); + + while ( outerRowSet.next() && innerRowSet.next() ) + { + for ( int i=1; i <= outerRowSet.getColumnCount(); ++i ) + { + assertTrue( "content of column " + i + " of row " + outerRowSet.getRow() + " not identical", + innerRowSet.getString(i).equals( outerRowSet.getString(i) ) ); + } + } + } + + + /** executes a SQL statement simply selecting all columns from a query + */ + @Test + public void executeSimpleSelect() throws SQLException + { + verifyEqualRowSetContent( + CommandType.COMMAND, "SELECT * FROM \"query products\"", + CommandType.QUERY,QUERY_PRODUCTS); + } + + + /** verifies that aliases for inner queries work as expected + */ + @Test + public void executeAliasedSelect() throws SQLException + { + verifyEqualRowSetContent( + CommandType.COMMAND, "SELECT \"PROD\".\"ID\" FROM \"query products\" AS \"PROD\"", + CommandType.COMMAND, "SELECT \"ID\" FROM \"products\"" ); + verifyEqualRowSetContent( + CommandType.COMMAND, "SELECT \"PROD\".* FROM \"query products\" AS \"PROD\"", + CommandType.QUERY,QUERY_PRODUCTS); + } + + + /** verifies that aliases for inner queries work as expected + */ + @Test + public void checkNameCollisions() + { + // create a query with a name which is used by a table + boolean caughtExpected = false; + try + { + m_database.getDatabase().getDataSource().createQuery( "products", "SELECT * FROM \"products\"" ); + } + catch ( WrappedTargetException e ) { caughtExpected = true; } + catch ( IllegalArgumentException e ) {} + catch ( ElementExistException e ) { caughtExpected = true; } + assertTrue( "creating queries with the name of an existing table should not be possible", + caughtExpected ); + + // create a table with a name which is used by a query + final HsqlTableDescriptor table = new HsqlTableDescriptor( QUERY_PRODUCTS, + new HsqlColumnDescriptor[] { + new HsqlColumnDescriptor( "ID", "INTEGER" ), + new HsqlColumnDescriptor( "Name", "VARCHAR(50)" ) } ); + + caughtExpected = false; + try + { + m_database.getDatabase().createTableInSDBCX( table ); + } + catch ( SQLException e ) { caughtExpected = true; } + catch ( ElementExistException ex ) { } + assertTrue( "creating tables with the name of an existing query should not be possible", + caughtExpected ); + } + + + @Test + public void checkCyclicReferences() throws ElementExistException, WrappedTargetException, IllegalArgumentException + { + // some queries which create a cycle in the sub query tree + m_database.getDatabase().getDataSource().createQuery( "orders level 1", "SELECT * FROM \"orders level 0\"" ); + m_database.getDatabase().getDataSource().createQuery( "orders level 2", "SELECT * FROM \"orders level 1\"" ); + m_database.getDatabase().getDataSource().createQuery( "orders level 3", "SELECT * FROM \"orders level 2\"" ); + m_database.getDatabase().getDataSource().createQuery( "orders level 0", "SELECT * FROM \"orders level 3\"" ); + + final RowSet rowSet = m_database.getDatabase().createRowSet( CommandType.QUERY, "orders level 0" ); + + boolean caughtExpected = false; + try { rowSet.execute(); } + catch ( SQLException e ) { caughtExpected = ( e.ErrorCode == -com.sun.star.sdb.ErrorCondition.PARSER_CYCLIC_SUB_QUERIES ); } + + assertTrue( "executing a query with cyclic nested sub queries should fail!", caughtExpected ); + } + + + @Test + public void checkStatementQiQSupport() + { + try + { + final XStatement statement = m_database.getConnection().createStatement(); + final XResultSet resultSet = statement.executeQuery( "SELECT * FROM \"query products\"" ); + assertTrue( "Result Set is null", resultSet != null ); + } + catch( SQLException e ) + { + fail( "SDB level statements do not allow for queries in queries" ); + } + } +} diff --git a/dbaccess/qa/complex/dbaccess/RowSet.java b/dbaccess/qa/complex/dbaccess/RowSet.java new file mode 100644 index 0000000000..ed42a271b4 --- /dev/null +++ b/dbaccess/qa/complex/dbaccess/RowSet.java @@ -0,0 +1,942 @@ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ +package complex.dbaccess; + +import com.sun.star.beans.UnknownPropertyException; +import com.sun.star.beans.XPropertySet; +import com.sun.star.container.XIndexAccess; +import com.sun.star.lang.WrappedTargetException; +import com.sun.star.lang.XComponent; +import com.sun.star.sdb.CommandType; +import com.sun.star.sdb.XParametersSupplier; +import com.sun.star.sdb.XResultSetAccess; +import com.sun.star.sdb.XRowSetApproveBroadcaster; +import com.sun.star.sdbc.SQLException; +import com.sun.star.sdbc.XParameters; +import com.sun.star.sdbc.XPreparedStatement; +import com.sun.star.sdbc.XResultSet; +import com.sun.star.sdbc.XResultSetUpdate; +import com.sun.star.sdbc.XRow; +import com.sun.star.sdbc.XRowSet; +import com.sun.star.sdbc.XRowUpdate; +import com.sun.star.sdbcx.XColumnsSupplier; +import com.sun.star.sdbcx.XDeleteRows; +import com.sun.star.sdbcx.XRowLocate; +import com.sun.star.uno.UnoRuntime; + +import connectivity.tools.CRMDatabase; +import connectivity.tools.DataSource; +import connectivity.tools.HsqlDatabase; +import connectivity.tools.sdb.Connection; +import java.lang.reflect.Method; +import java.util.Random; + +// ---------- junit imports ----------------- +import org.junit.After; +import org.junit.Test; +import static org.junit.Assert.*; + + +public class RowSet extends TestCase +{ + + static final int MAX_TABLE_ROWS = 100; + static final int MAX_FETCH_ROWS = 10; + private static final String NEXT = "next"; + private static final String TEST21 = "Test21"; + HsqlDatabase m_database; + DataSource m_dataSource; + XRowSet m_rowSet; + XResultSet m_resultSet; + XResultSetUpdate m_resultSetUpdate; + XRow m_row; + XRowLocate m_rowLocate; + XPropertySet m_rowSetProperties; + XParametersSupplier m_paramsSupplier; + + private final Object failedResultSetMovementStressGuard = new Object(); + private String failedResultSetMovementStressMessages = ""; + + private class ResultSetMovementStress implements Runnable + { + + private final XResultSet m_resultSet; + private final XRow m_row; + private final int m_id; + + private ResultSetMovementStress(XResultSet _resultSet, int _id) throws java.lang.Exception + { + m_resultSet = _resultSet; + m_row = UnoRuntime.queryInterface( XRow.class, m_resultSet ); + m_id = _id; + } + + public void run() + { + int i=-1; + try + { + m_resultSet.beforeFirst(); + for (i = 0; m_resultSet.next(); ++i) + { + int pos = m_resultSet.getRow(); + testPosition(m_resultSet, m_row, i + 1, "clone move(" + m_id + ")"); + int pos2 = m_resultSet.getRow(); + assertTrue("ResultSetMovementStress wrong position: " + i + " Pos1: " + pos + " Pos2: " + pos2, pos == pos2); + } + } + catch (Exception e) + { + synchronized (failedResultSetMovementStressGuard) { + failedResultSetMovementStressMessages + += "ResultSetMovementStress(" + m_id + ") failed at i=" + + i + ": " + e + "\n"; + } + } + } + } + + private void createTestCase(boolean _defaultRowSet) throws Exception + { + if (m_database == null) + { + final CRMDatabase database = new CRMDatabase( getMSF(), false ); + m_database = database.getDatabase(); + m_dataSource = m_database.getDataSource(); + } + + createStructure(); + + if (_defaultRowSet) + { + createRowSet("TEST1", CommandType.TABLE, true, true); + } + } + + @After public final void closeAndDeleteDatabase() { + if (m_database != null) { + m_database.closeAndDelete(); + } + } + + /** creates a com.sun.star.sdb.RowSet to use during the test + * @param command + * the command to use for the RowSet + * @param commandType + * the command type to use for the RowSet + * @param execute + * determines whether the RowSet should be executed + */ + private void createRowSet(String command, int commandType, boolean execute) throws com.sun.star.uno.Exception + { + createRowSet(command, commandType, execute, false); + } + + + /** creates a com.sun.star.sdb.RowSet to use during the test + * @param command + * the command to use for the RowSet + * @param commandType + * the command type to use for the RowSet + * @param execute + * determines whether the RowSet should be executed + * @param limitFetchSize + * determines whether the fetch size of the RowSet should be limited to MAX_FETCH_ROWS + */ + private void createRowSet(String command, int commandType, boolean execute, boolean limitFetchSize) throws com.sun.star.uno.Exception + { + m_rowSet = UnoRuntime.queryInterface( XRowSet.class, getMSF().createInstance( "com.sun.star.sdb.RowSet" ) ); + final XPropertySet rowSetProperties = UnoRuntime.queryInterface( XPropertySet.class, m_rowSet ); + rowSetProperties.setPropertyValue("Command", command); + rowSetProperties.setPropertyValue("CommandType", Integer.valueOf(commandType)); + rowSetProperties.setPropertyValue("ActiveConnection", m_database.defaultConnection().getXConnection()); + if (limitFetchSize) + { + rowSetProperties.setPropertyValue("FetchSize", Integer.valueOf(MAX_FETCH_ROWS)); + } + + m_resultSet = UnoRuntime.queryInterface( XResultSet.class, m_rowSet ); + m_resultSetUpdate = UnoRuntime.queryInterface( XResultSetUpdate.class, m_rowSet ); + m_row = UnoRuntime.queryInterface( XRow.class, m_rowSet ); + m_rowLocate = UnoRuntime.queryInterface( XRowLocate.class, m_resultSet ); + m_rowSetProperties = UnoRuntime.queryInterface( XPropertySet.class, m_rowSet ); + m_paramsSupplier = UnoRuntime.queryInterface( XParametersSupplier.class, m_rowSet ); + + if (execute) + { + m_rowSet.execute(); + } + } + + + @Test + public void testRowSet() throws java.lang.Exception + { + + System.out.println("testing testRowSet"); + createTestCase(true); + + // sequential positioning + m_resultSet.beforeFirst(); + testSequentialPositining(m_resultSet, m_row); + + // absolute positioning + testAbsolutePositioning(m_resultSet, m_row); + + // position during modify + testModifyPosition(m_resultSet, m_row); + + // 3rd test + test3(createClone(), m_resultSet); + // 4th test + test4(m_resultSet); + + // concurrent (multi threaded) access to the row set and its clones + testConcurrentAccess(m_resultSet); + } + + + XResultSet createClone() throws SQLException + { + final XResultSetAccess rowAcc = UnoRuntime.queryInterface( XResultSetAccess.class, m_rowSet ); + return rowAcc.createResultSet(); + } + + + void createStructure() throws SQLException + { + m_database.executeSQL("DROP TABLE \"TEST1\" IF EXISTS"); + m_database.executeSQL("CREATE TABLE \"TEST1\" (\"ID\" integer not null primary key, \"col2\" varchar(50) )"); + + final Connection connection = m_database.defaultConnection(); + final XPreparedStatement prep = connection.prepareStatement("INSERT INTO \"TEST1\" values (?,?)"); + final XParameters para = UnoRuntime.queryInterface( XParameters.class, prep ); + for (int i = 1; i <= MAX_TABLE_ROWS; ++i) + { + para.setInt(1, i); + para.setString(2, "Test" + i); + prep.executeUpdate(); + } + + connection.refreshTables(); + } + + + void testPosition(XResultSet resultSet, XRow row, int expectedValue, String location) throws SQLException + { + final int val = row.getInt(1); + final int pos = resultSet.getRow(); + assertTrue(location + ": value/position do not match: " + pos + " (pos) != " + val + " (val)", val == pos); + assertTrue(location + ": value/position are not as expected: " + val + " (val) != " + expectedValue + " (expected)", val == expectedValue); + } + + + void testSequentialPositining(XResultSet _resultSet, XRow _row) throws com.sun.star.uno.Exception + { + // 1st test + int i = 1; + while (_resultSet.next()) + { + testPosition(_resultSet, _row, i, "testSequentialPositining"); + ++i; + } + } + + + void testAbsolutePositioning(XResultSet _resultSet, XRow _row) throws com.sun.star.uno.Exception + { + for (int i = 1; i <= MAX_FETCH_ROWS; ++i) + { + final int calcPos = (MAX_TABLE_ROWS % i) + 1; + assertTrue("testAbsolutePositioning failed", _resultSet.absolute(calcPos)); + testPosition(_resultSet, _row, calcPos, "testAbsolutePositioning"); + } + } + + + void testModifyPosition(XResultSet _resultSet, XRow _row) throws com.sun.star.uno.Exception + { + final int testPos = 3; + assertTrue("testModifyPosition wants at least " + (testPos+1) + " rows", MAX_FETCH_ROWS >= testPos+1); + assertTrue("testModifyPosition failed on moving to row " + testPos, _resultSet.absolute(testPos)); + UnoRuntime.queryInterface( XRowUpdate.class, _row ).updateString(2, TEST21); + testPosition(_resultSet, _row, testPos, "testModifyPosition"); + UnoRuntime.queryInterface( XResultSetUpdate.class, _resultSet ).cancelRowUpdates(); + } + + + void test3(XResultSet clone, XResultSet _resultSet) throws com.sun.star.uno.Exception + { + final XRow _row = UnoRuntime.queryInterface( XRow.class, _resultSet ); + final XRow cloneRow = UnoRuntime.queryInterface( XRow.class, clone ); + for (int i = 1; i <= MAX_FETCH_ROWS; ++i) + { + final int calcPos = (MAX_TABLE_ROWS % i) + 1; + if (clone.absolute(calcPos)) + { + testPosition(clone, cloneRow, calcPos, "test3"); + testAbsolutePositioning(_resultSet, _row); + testAbsolutePositioning(clone, cloneRow); + } + } + } + + + void test4(XResultSet _resultSet) throws com.sun.star.uno.Exception + { + final XRow _row = UnoRuntime.queryInterface( XRow.class, _resultSet ); + _resultSet.beforeFirst(); + + for (int i = 1; i <= MAX_TABLE_ROWS; ++i) + { + _resultSet.next(); + final XResultSet clone = createClone(); + final XRow cloneRow = UnoRuntime.queryInterface( XRow.class, clone ); + final int calcPos = MAX_TABLE_ROWS - 1; + if (calcPos != 0 && clone.absolute(calcPos)) + { + testPosition(clone, cloneRow, calcPos, "test4: clone"); + testPosition(_resultSet, _row, i, "test4: rowset"); + } + } + } + + + void testConcurrentAccess(XResultSet _resultSet) throws Exception + { + System.out.println("testing Thread"); + + _resultSet.beforeFirst(); + + final int numberOfThreads = 10; + + final Thread threads[] = new Thread[numberOfThreads]; + for (int i = 0; i < numberOfThreads; ++i) + { + threads[i] = new Thread(new ResultSetMovementStress(createClone(), i)); + System.out.println("starting thread " + (i + 1) + " of " + (numberOfThreads)); + threads[i].start(); + } + + for (int i = 0; i < numberOfThreads; ++i) + { + threads[i].join(); + } + synchronized (failedResultSetMovementStressGuard) { + assertEquals("", failedResultSetMovementStressMessages); + } + } + + + @Test + public void testRowSetEvents() throws java.lang.Exception + { + System.out.println("testing RowSet Events"); + createTestCase(true); + + // first we create our RowSet object + final RowSetEventListener pRow = new RowSetEventListener(); + + final XColumnsSupplier colSup = UnoRuntime.queryInterface( XColumnsSupplier.class, m_rowSet ); + final XPropertySet col = UnoRuntime.queryInterface( XPropertySet.class, colSup.getColumns().getByName( "ID" ) ); + col.addPropertyChangeListener("Value", pRow); + m_rowSetProperties.addPropertyChangeListener("IsModified", pRow); + m_rowSetProperties.addPropertyChangeListener("IsNew", pRow); + m_rowSetProperties.addPropertyChangeListener("IsRowCountFinal", pRow); + m_rowSetProperties.addPropertyChangeListener("RowCount", pRow); + + final XRowSetApproveBroadcaster xApBroad = UnoRuntime.queryInterface( XRowSetApproveBroadcaster.class, m_resultSet ); + xApBroad.addRowSetApproveListener(pRow); + m_rowSet.addRowSetListener(pRow); + + // do some movements to check if we got all notifications + final Class<?> cResSet = Class.forName("com.sun.star.sdbc.XResultSet"); + final boolean moves[] = new boolean[9]; + for (int i = 0; i < moves.length; ++i) + { + moves[i] = false; + } + moves[RowSetEventListener.APPROVE_CURSOR_MOVE] = true; + moves[RowSetEventListener.COLUMN_VALUE] = true; + moves[RowSetEventListener.CURSOR_MOVED] = true; + moves[RowSetEventListener.IS_ROW_COUNT_FINAL] = true; + moves[RowSetEventListener.ROW_COUNT] = true; + + testCursorMove(m_resultSet, cResSet.getMethod("afterLast", (Class[]) null), pRow, moves, null); + + moves[RowSetEventListener.IS_ROW_COUNT_FINAL] = false; + moves[RowSetEventListener.ROW_COUNT] = false; + testCursorMove(m_resultSet, cResSet.getMethod(NEXT, (Class[]) null), pRow, moves, null); + testCursorMove(m_resultSet, cResSet.getMethod(NEXT, (Class[]) null), pRow, moves, null); + testCursorMove(m_resultSet, cResSet.getMethod(NEXT, (Class[]) null), pRow, moves, null); + testCursorMove(m_resultSet, cResSet.getMethod("last", (Class[]) null), pRow, moves, null); + testCursorMove(m_resultSet, cResSet.getMethod(NEXT, (Class[]) null), pRow, moves, null); + testCursorMove(m_resultSet, cResSet.getMethod("first", (Class[]) null), pRow, moves, null); + testCursorMove(m_resultSet, cResSet.getMethod("previous", (Class[]) null), pRow, moves, null); + testCursorMove(m_resultSet, cResSet.getMethod(NEXT, (Class[]) null), pRow, moves, null); + moves[RowSetEventListener.IS_MODIFIED] = true; + final XRowUpdate updRow = UnoRuntime.queryInterface( XRowUpdate.class, m_resultSet ); + updRow.updateString(2, TEST21); + testCursorMove(m_resultSet, cResSet.getMethod(NEXT, (Class[]) null), pRow, moves, null); + + moves[RowSetEventListener.IS_MODIFIED] = false; + updRow.updateString(2, m_row.getString(2)); + testCursorMove(m_resultSet, cResSet.getMethod(NEXT, (Class[]) null), pRow, moves, null); + + moves[RowSetEventListener.IS_MODIFIED] = false; + final Class<?> cupd = Class.forName("com.sun.star.sdbc.XResultSetUpdate"); + final XResultSetUpdate upd = UnoRuntime.queryInterface( XResultSetUpdate.class, m_resultSet ); + testCursorMove(upd, cupd.getMethod("moveToInsertRow", (Class[]) null), pRow, moves, null); + + updRow.updateInt(1, MAX_TABLE_ROWS + 2); + updRow.updateString(2, "HHHH"); + moves[RowSetEventListener.APPROVE_CURSOR_MOVE] = false; + moves[RowSetEventListener.CURSOR_MOVED] = false; + moves[RowSetEventListener.IS_MODIFIED] = true; + moves[RowSetEventListener.IS_NEW] = true; + moves[RowSetEventListener.ROW_COUNT] = true; + moves[RowSetEventListener.APPROVE_ROW_CHANGE] = true; + moves[RowSetEventListener.ROW_CHANGED] = true; + testCursorMove(upd, cupd.getMethod("insertRow", (Class[]) null), pRow, moves, null); + + moves[RowSetEventListener.IS_NEW] = false; + moves[RowSetEventListener.ROW_COUNT] = false; + m_resultSet.first(); + updRow.updateInt(1, MAX_TABLE_ROWS + 3); + updRow.updateString(2, "__"); + testCursorMove(upd, cupd.getMethod("updateRow", (Class[]) null), pRow, moves, null); + + moves[RowSetEventListener.IS_NEW] = true; + moves[RowSetEventListener.ROW_COUNT] = true; + m_resultSet.first(); + testCursorMove(upd, cupd.getMethod("deleteRow", (Class[]) null), pRow, moves, null); + + moves[RowSetEventListener.IS_NEW] = false; + moves[RowSetEventListener.COLUMN_VALUE] = true; + moves[RowSetEventListener.ROW_COUNT] = false; + m_resultSet.first(); + updRow.updateString(2, TEST21); + testCursorMove(m_resultSet, cResSet.getMethod("refreshRow", (Class[]) null), pRow, moves, null); + + m_resultSet.first(); + updRow.updateString(2, TEST21); + testCursorMove(upd, cupd.getMethod("cancelRowUpdates", (Class[]) null), pRow, moves, null); + + for (int i = 0; i < moves.length; ++i) + { + moves[i] = false; + } + moves[RowSetEventListener.APPROVE_CURSOR_MOVE] = true; + moves[RowSetEventListener.COLUMN_VALUE] = true; + moves[RowSetEventListener.CURSOR_MOVED] = true; + + final Class<?> cloc = Class.forName("com.sun.star.sdbcx.XRowLocate"); + m_resultSet.first(); + final Object bookmark = m_rowLocate.getBookmark(); + m_resultSet.next(); + final Object temp[] = new Object[1]; + temp[0] = bookmark; + Class ctemp[] = new Class[1]; + ctemp[0] = Object.class; + testCursorMove(m_rowLocate, cloc.getMethod("moveToBookmark", ctemp), pRow, moves, temp); + + final Object temp2[] = new Object[2]; + temp2[0] = bookmark; + temp2[1] = Integer.valueOf(1); + final Class ctemp2[] = new Class[2]; + ctemp2[0] = Object.class; + ctemp2[1] = int.class; + testCursorMove(m_rowLocate, cloc.getMethod("moveRelativeToBookmark", ctemp2), pRow, moves, temp2); + + for (int i = 0; i < moves.length; ++i) + { + moves[i] = false; + } + moves[RowSetEventListener.APPROVE_ROW_CHANGE] = true; + moves[RowSetEventListener.ROW_CHANGED] = true; + moves[RowSetEventListener.ROW_COUNT] = true; + final Class<?> cdelRows = Class.forName("com.sun.star.sdbcx.XDeleteRows"); + ctemp[0] = Object[].class; + final XDeleteRows delRows = UnoRuntime.queryInterface( XDeleteRows.class, m_resultSet ); + final Object bookmarks[] = new Object[5]; + m_resultSet.first(); + for (int i = 0; i < bookmarks.length; ++i) + { + m_resultSet.next(); + bookmarks[i] = m_rowLocate.getBookmark(); + } + + temp[0] = bookmarks; + testCursorMove(delRows, cdelRows.getMethod("deleteRows", ctemp), pRow, moves, temp); + + // now destroy the RowSet + final XComponent xComp = UnoRuntime.queryInterface( XComponent.class, m_resultSet ); + xComp.dispose(); + } + + + private void testCursorMove(Object res, Method _method, RowSetEventListener _evt, boolean _must[], Object args[]) throws java.lang.Exception + { + _evt.clearCalling(); + _method.invoke(res, args); + + System.out.println("testing events for " + _method.getName()); + final int calling[] = _evt.getCalling(); + int pos = 1; + assertTrue("Callings are not in the correct order for APPROVE_CURSOR_MOVE ", + (!_must[RowSetEventListener.APPROVE_CURSOR_MOVE] || calling[RowSetEventListener.APPROVE_CURSOR_MOVE] == -1) || calling[RowSetEventListener.APPROVE_CURSOR_MOVE] == pos++); + assertTrue("Callings are not in the correct order for APPROVE_ROW_CHANGE", + (!_must[RowSetEventListener.APPROVE_ROW_CHANGE] || calling[RowSetEventListener.APPROVE_ROW_CHANGE] == -1) || calling[RowSetEventListener.APPROVE_ROW_CHANGE] == pos++); + assertTrue("Callings are not in the correct order for COLUMN_VALUE", + (!_must[RowSetEventListener.COLUMN_VALUE] || calling[RowSetEventListener.COLUMN_VALUE] == -1) || calling[RowSetEventListener.COLUMN_VALUE] == pos++); + assertTrue("Callings are not in the correct order for CURSOR_MOVED", + (!_must[RowSetEventListener.CURSOR_MOVED] || calling[RowSetEventListener.CURSOR_MOVED] == -1) || calling[RowSetEventListener.CURSOR_MOVED] == pos++); + assertTrue("Callings are not in the correct order for ROW_CHANGED", + (!_must[RowSetEventListener.ROW_CHANGED] || calling[RowSetEventListener.ROW_CHANGED] == -1) || calling[RowSetEventListener.ROW_CHANGED] == pos++); + assertTrue("Callings are not in the correct order for IS_MODIFIED", + (!_must[RowSetEventListener.IS_MODIFIED] || calling[RowSetEventListener.IS_MODIFIED] == -1) || calling[RowSetEventListener.IS_MODIFIED] == pos++); + assertTrue("Callings are not in the correct order for IS_NEW", + (!_must[RowSetEventListener.IS_NEW] || calling[RowSetEventListener.IS_NEW] == -1) || calling[RowSetEventListener.IS_NEW] == pos++); + assertTrue("Callings are not in the correct order for ROW_COUNT", + (!_must[RowSetEventListener.ROW_COUNT] || calling[RowSetEventListener.ROW_COUNT] == -1) || calling[RowSetEventListener.ROW_COUNT] == pos++); + assertTrue("Callings are not in the correct order for IS_ROW_COUNT_FINAL", + (!_must[RowSetEventListener.IS_ROW_COUNT_FINAL] || calling[RowSetEventListener.IS_ROW_COUNT_FINAL] == -1) || calling[RowSetEventListener.IS_ROW_COUNT_FINAL] == pos); + + _evt.clearCalling(); + } + + + /** returns the current row count of the RowSet + */ + private int currentRowCount() throws UnknownPropertyException, WrappedTargetException + { + final Integer rowCount = (Integer) m_rowSetProperties.getPropertyValue("RowCount"); + return rowCount.intValue(); + } + + + /** positions the row set at an arbitrary position between 2 and (current row count - 1) + */ + private int positionRandom() throws SQLException, UnknownPropertyException, WrappedTargetException + { + // note: obviously this should subtract 2 but actually subtract 3 + // because if we have just deleted the current row then + // ORowSetBase::impl_getRowCount() will lie and currentRowCount() + // returns 1 more than the actual number of rows and then + // positionRandom() followed by deleteRow() deletes *last* row + final int position = (new Random()).nextInt(currentRowCount() - 3) + 2; + assertTrue("sub task failed: could not position to row no. " + (Integer.valueOf(position)).toString(), + m_resultSet.absolute(position)); + return m_resultSet.getRow(); + } + + + /** moves the result set to a random record between 2 and (current row count - 1), and deletes this record + * + * After returning from this method, the row set is still positioned at the deleted record + * @return + * the number/position of the record which has been deleted + */ + private int deleteRandom() throws SQLException, UnknownPropertyException, WrappedTargetException + { + // check if the current position and the row count in the result set is changed by a deletion (it should not) + final int positionBefore = positionRandom(); + final int rowCountBefore = currentRowCount(); + + m_resultSetUpdate.deleteRow(); + + final int positionAfter = m_resultSet.getRow(); + final int rowCountAfter = currentRowCount(); + assertTrue("position changed during |deleteRow| (it should not)", positionAfter == positionBefore); + assertTrue("row count changed with a |deleteRow| (it should not)", rowCountBefore == rowCountAfter); + assertTrue("RowSet does not report the current row as deleted after |deleteRow|", m_resultSet.rowDeleted()); + + return positionBefore; + } + + + @Test + public void testDeleteBehavior() throws Exception + { + createTestCase(true); + + // ensure that all records are known + m_resultSet.last(); + final int initialRowCount = currentRowCount(); + + // delete a random row + int deletedRow = deleteRandom(); + + + // asking for the bookmark of a deleted row should fail + boolean caughtException = false; + try + { + m_rowLocate.getBookmark(); + } + catch (SQLException e) + { + caughtException = true; + } + assertTrue("asking for the bookmark of a deleted row should throw an exception", caughtException); + + + // isXXX methods should return |false| on a deleted row + assertTrue("one of the isFoo failed after |deleteRow|", !m_resultSet.isBeforeFirst() && !m_resultSet.isAfterLast() && !m_resultSet.isFirst() && !m_resultSet.isLast()); + // note that we can assume that isFirst / isLast also return |false|, since deleteRandom did + // not position on the first or last record, but inbetween + + + // check if moving away from this row in either direction yields the expected results + assertTrue("|previous| after |deleteRow| failed", m_resultSet.previous()); + final int positionPrevious = m_resultSet.getRow(); + assertTrue("position after |previous| after |deleteRow| is not as expected", positionPrevious == deletedRow - 1); + + deletedRow = deleteRandom(); + assertTrue("|next| after |deleteRow| failed", m_resultSet.next()); + final int positionAfter = m_resultSet.getRow(); + assertTrue("position after |next| after |deleteRow| is not as expected", positionAfter == deletedRow); + // since the deleted record "vanishes" as soon as the cursor is moved away from it, the absolute position does + // not change with a |next| call here + + + // check if the deleted rows really vanished after moving away from them + assertTrue("row count did not change as expected after two deletions", initialRowCount - 2 == currentRowCount()); + + + // check if the deleted row vanishes after moving to the insertion row + final int rowCountBefore = currentRowCount(); + final int deletedPos = deleteRandom(); + m_resultSetUpdate.moveToInsertRow(); + assertTrue("moving to the insertion row immediately after |deleteRow| does not adjust the row count", rowCountBefore == currentRowCount() + 1); + + m_resultSetUpdate.moveToCurrentRow(); + assertTrue("|moveToCurrentRow| after |deleteRow| + |moveToInsertRow| results in unexpected position", + (m_resultSet.getRow() == deletedPos) && !m_resultSet.rowDeleted()); + + // the same, but this time with deleting the first row (which is not covered by deleteRandom) + m_resultSet.last(); + m_resultSetUpdate.deleteRow(); + m_resultSetUpdate.moveToInsertRow(); + m_resultSetUpdate.moveToCurrentRow(); + assertTrue("|last| + |deleteRow| + |moveToInsertRow| + |moveToCurrentRow| results in wrong state", m_resultSet.isAfterLast()); + + + // check if deleting a deleted row fails as expected + deleteRandom(); + caughtException = false; + try + { + m_resultSetUpdate.deleteRow(); + } + catch (SQLException e) + { + caughtException = true; + } + assertTrue("deleting a deleted row succeeded - it shouldn't", caughtException); + + + // check if deleteRows fails if it contains the bookmark of a previously-deleted row + m_resultSet.first(); + final Object firstBookmark = m_rowLocate.getBookmark(); + positionRandom(); + final Object deleteBookmark = m_rowLocate.getBookmark(); + m_resultSetUpdate.deleteRow(); + final XDeleteRows multiDelete = UnoRuntime.queryInterface( XDeleteRows.class, m_resultSet ); + final int[] deleteSuccess = multiDelete.deleteRows(new Object[] + { + firstBookmark, deleteBookmark + }); + assertTrue("XDeleteRows::deleteRows with the bookmark of an already-deleted row failed", + (deleteSuccess.length == 2) && (deleteSuccess[0] != 0) && (deleteSuccess[1] == 0)); + + + // check if refreshing a deleted row fails as expected + deleteRandom(); + caughtException = false; + try + { + m_resultSet.refreshRow(); + } + catch (SQLException e) + { + caughtException = true; + } + assertTrue("refreshing a deleted row succeeded - it shouldn't", caughtException); + + + // rowUpdated/rowDeleted + deleteRandom(); + assertTrue("rowDeleted and/or rowUpdated are wrong on a deleted row", !m_resultSet.rowUpdated() && !m_resultSet.rowInserted()); + + + // updating values in a deleted row should fail + deleteRandom(); + final XRowUpdate rowUpdated = UnoRuntime.queryInterface( XRowUpdate.class, m_resultSet ); + caughtException = false; + try + { + rowUpdated.updateString(2, TEST21); + } + catch (SQLException e) + { + caughtException = true; + } + assertTrue("updating values in a deleted row should not succeed", caughtException); + } + + + /** checks whether deletions on the main RowSet properly interfere (or don't interfere) with the movement + * on a clone of the RowSet + */ + @Test + public void testCloneMovesPlusDeletions() throws Exception + { + createTestCase(true); + // ensure that all records are known + m_resultSet.last(); + + final XResultSet clone = createClone(); + final XRowLocate cloneRowLocate = UnoRuntime.queryInterface( XRowLocate.class, clone ); + + positionRandom(); + + + // move the clone to the same record as the RowSet, and delete this record + cloneRowLocate.moveToBookmark(m_rowLocate.getBookmark()); + final int clonePosition = clone.getRow(); + m_resultSetUpdate.deleteRow(); + + assertTrue("clone doesn't know that its current row has been deleted via the RowSet", clone.rowDeleted()); + assertTrue("clone's position changed somehow during deletion", clonePosition == clone.getRow()); + + + // move the row set away from the deleted record. This should still not touch the state of the clone + m_resultSet.previous(); + + assertTrue("clone doesn't know (anymore) that its current row has been deleted via the RowSet", clone.rowDeleted()); + assertTrue("clone's position changed somehow during deletion and RowSet-movement", clonePosition == clone.getRow()); + + + // move the clone away from the deleted record + clone.next(); + assertTrue("clone still assumes that its row is deleted - but we already moved it", !clone.rowDeleted()); + + + // check whether deleting the extremes (first / last) work + m_resultSet.first(); + cloneRowLocate.moveToBookmark(m_rowLocate.getBookmark()); + m_resultSetUpdate.deleteRow(); + clone.previous(); + assertTrue("deleting the first record left the clone in a strange state (after |previous|)", clone.isBeforeFirst()); + clone.next(); + assertTrue("deleting the first record left the clone in a strange state (after |previous| + |next|)", clone.isFirst()); + + m_resultSet.last(); + cloneRowLocate.moveToBookmark(m_rowLocate.getBookmark()); + m_resultSetUpdate.deleteRow(); + clone.next(); + assertTrue("deleting the last record left the clone in a strange state (after |next|)", clone.isAfterLast()); + clone.previous(); + assertTrue("deleting the first record left the clone in a strange state (after |next| + |previous|)", clone.isLast()); + + + // check whether movements of the clone interfere with movements of the RowSet, if the latter is on a deleted row + final int positionBefore = positionRandom(); + m_resultSetUpdate.deleteRow(); + assertTrue("|deleteRow|, but no |rowDeleted| (this should have been found much earlier!)", m_resultSet.rowDeleted()); + clone.beforeFirst(); + while (clone.next()) {} + assertTrue("row set forgot that the current row is deleted", m_resultSet.rowDeleted()); + + assertTrue("moving to the next record after |deleteRow| and clone moves failed", m_resultSet.next()); + assertTrue("wrong position after |deleteRow| and clone movement", !m_resultSet.isAfterLast() && !m_resultSet.isBeforeFirst()); + assertTrue("wrong absolute position after |deleteRow| and clone movement", m_resultSet.getRow() == positionBefore); + } + + + /** checks whether insertions on the main RowSet properly interfere (or don't interfere) with the movement + * on a clone of the RowSet + */ + @Test + public void testCloneMovesPlusInsertions() throws Exception + { + createTestCase(true); + // ensure that all records are known + m_rowSetProperties.setPropertyValue("FetchSize", Integer.valueOf(10)); + + final XResultSet clone = createClone(); + final XRow cloneRow = UnoRuntime.queryInterface( XRow.class, clone ); + + + // first check the basic scenario without the |moveToInsertRow| |moveToCurrentRow|, to ensure that + // really those are broken, if at all + m_resultSet.last(); + clone.first(); + clone.absolute(11); + clone.first(); + + final int rowValue1 = m_row.getInt(1); + final int rowPos = m_resultSet.getRow(); + final int rowValue2 = m_row.getInt(1); + assertTrue("repeated query for the same column value delivers different values (" + rowValue1 + " and " + rowValue2 + ") on row: " + rowPos, + rowValue1 == rowValue2); + + testPosition(clone, cloneRow, 1, "mixed clone/rowset move: clone check"); + testPosition(m_resultSet, m_row, MAX_TABLE_ROWS, "mixed clone/rowset move: rowset check"); + + + // now the complete scenario + m_resultSet.last(); + m_resultSetUpdate.moveToInsertRow(); + clone.first(); + clone.absolute(11); + clone.first(); + m_resultSetUpdate.moveToCurrentRow(); + + testPosition(clone, cloneRow, 1, "mixed clone/rowset move/insertion: clone check"); + testPosition(m_resultSet, m_row, 100, "mixed clone/rowset move/insertion: rowset check"); + } + + + private void testTableParameters() throws com.sun.star.uno.Exception + { + // for a row set simply based on a table, there should be not parameters at all + createRowSet("products", CommandType.TABLE, false); + verifyParameters(new String[] + { + }, "testTableParameters"); + } + + + private void testParametersAfterNormalExecute() throws com.sun.star.uno.Exception + { + createRowSet("SELECT * FROM \"customers\"", CommandType.COMMAND, true); + m_rowSetProperties.setPropertyValue("Command", "SELECT * FROM \"customers\" WHERE \"City\" = :city"); + final XParameters rowsetParams = UnoRuntime.queryInterface( XParameters.class, m_rowSet ); + rowsetParams.setString(1, "London"); + m_rowSet.execute(); + } + + + private void verifyParameters(String[] _paramNames, String _context) throws com.sun.star.uno.Exception + { + final XIndexAccess params = m_paramsSupplier.getParameters(); + final int expected = _paramNames.length; + final int found = params != null ? params.getCount() : 0; + + assertTrue("wrong number of parameters (expected: " + expected + ", found: " + found + ") in " + _context, + found == expected); + + if (found == 0) + { + return; + } + + for (int i = 0; i < expected; ++i) + { + final XPropertySet parameter = UnoRuntime.queryInterface( XPropertySet.class, params.getByIndex( i ) ); + + final String expectedName = _paramNames[i]; + final String foundName = (String) parameter.getPropertyValue("Name"); + assertTrue("wrong parameter name (expected: " + expectedName + ", found: " + foundName + ") in" + _context, + expectedName.equals(foundName)); + } + } + + + private void testParametrizedQuery() throws com.sun.star.uno.Exception + { + // for a row set based on a parametrized query, those parameters should be properly + // recognized + m_dataSource.createQuery("products like", "SELECT * FROM \"products\" WHERE \"Name\" LIKE :product_name"); + createRowSet("products like", CommandType.QUERY, false); + verifyParameters(new String[] + { + "product_name" + }, "testParametrizedQuery"); + } + + + private void testParametersInteraction() throws com.sun.star.uno.Exception + { + createRowSet("products like", CommandType.QUERY, false); + + // let's fill in a parameter value via XParameters, and see whether it is respected by the parameters container + final XParameters rowsetParams = UnoRuntime.queryInterface(XParameters.class, m_rowSet); + rowsetParams.setString(1, "Apples"); + + XIndexAccess params = m_paramsSupplier.getParameters(); + XPropertySet firstParam = UnoRuntime.queryInterface( XPropertySet.class, params.getByIndex( 0 ) ); + Object firstParamValue = firstParam.getPropertyValue("Value"); + + assertTrue("XParameters and the parameters container do not properly interact", + "Apples".equals(firstParamValue)); + + // let's see whether this also survives an execute of the row set + rowsetParams.setString(1, "Oranges"); + m_rowSet.execute(); + { + // TODO: the following would not be necessary if the parameters container would *survive* + // the execution of the row set. It currently doesn't (though the values it represents do). + // It would be nice, but not strictly necessary, if it would. + params = m_paramsSupplier.getParameters(); + firstParam = UnoRuntime.queryInterface( XPropertySet.class, params.getByIndex( 0 ) ); + } + firstParamValue = firstParam.getPropertyValue("Value"); + assertTrue("XParameters and the parameters container do not properly interact, after the row set has been executed", + "Oranges".equals(firstParamValue)); + } + + + private void testParametersInFilter() throws com.sun.star.uno.Exception + { + createRowSet("SELECT * FROM \"customers\"", CommandType.COMMAND, false); + m_rowSetProperties.setPropertyValue("Filter", "\"City\" = :city"); + + m_rowSetProperties.setPropertyValue("ApplyFilter", Boolean.TRUE); + verifyParameters(new String[] + { + "city" + }, "testParametersInFilter"); + + m_rowSetProperties.setPropertyValue("ApplyFilter", Boolean.FALSE); + verifyParameters(new String[] + { + }, "testParametersInFilter"); + } + + + /** checks the XParametersSupplier functionality of a RowSet + */ + @Test + public void testParameters() throws Exception + { + createTestCase(false); + // use an own RowSet instance, not the one which is also used for the other cases + + testTableParameters(); + testParametrizedQuery(); + testParametersInFilter(); + + testParametersAfterNormalExecute(); + + testParametersInteraction(); + } +} + diff --git a/dbaccess/qa/complex/dbaccess/RowSetEventListener.java b/dbaccess/qa/complex/dbaccess/RowSetEventListener.java new file mode 100644 index 0000000000..991dfb7af7 --- /dev/null +++ b/dbaccess/qa/complex/dbaccess/RowSetEventListener.java @@ -0,0 +1,102 @@ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +package complex.dbaccess; + +import com.sun.star.sdb.XRowSetApproveListener; +import com.sun.star.sdbc.XRowSetListener; +import com.sun.star.sdb.RowChangeEvent; +import com.sun.star.lang.EventObject; +import com.sun.star.beans.XPropertyChangeListener; + +public final class RowSetEventListener implements XRowSetApproveListener,XRowSetListener,XPropertyChangeListener +{ + public static final int APPROVE_CURSOR_MOVE = 0; + public static final int APPROVE_ROW_CHANGE = 1; + public static final int COLUMN_VALUE = 2; + public static final int CURSOR_MOVED = 3; + public static final int ROW_CHANGED = 4; + public static final int IS_MODIFIED = 5; + public static final int IS_NEW = 6; + public static final int ROW_COUNT = 7; + public static final int IS_ROW_COUNT_FINAL = 8; + + private int callPos = 1; + private int calling []; + + RowSetEventListener(){ + calling = new int [9]; + clearCalling(); + } + public int[] getCalling(){ + return calling; + } + public void clearCalling(){ + for(int i = 0 ; i < calling.length; ++i){ + calling[i] = -1; + } + callPos = 1; + } + // XEventListener + public void disposing(com.sun.star.lang.EventObject event) + { + } + // XRowSetApproveBroadcaster + public boolean approveCursorMove(EventObject event) + { + calling[APPROVE_CURSOR_MOVE] = callPos++; + return true; + } + public boolean approveRowChange(RowChangeEvent event) + { + calling[APPROVE_ROW_CHANGE] = callPos++; + return true; + } + public boolean approveRowSetChange(EventObject event) + { + return true; + } + + // XRowSetListener + public void cursorMoved(com.sun.star.lang.EventObject event) + { + calling[CURSOR_MOVED] = callPos++; + } + public void rowChanged(com.sun.star.lang.EventObject event) + { + calling[ROW_CHANGED] = callPos++; + } + public void rowSetChanged(com.sun.star.lang.EventObject event) + { + } + + public void propertyChange(com.sun.star.beans.PropertyChangeEvent propertyChangeEvent) { + if ( "Value".equals(propertyChangeEvent.PropertyName) ){ + calling[COLUMN_VALUE] = callPos++; + } else if ( "IsModified".equals(propertyChangeEvent.PropertyName) ){ + calling[IS_MODIFIED] = callPos++; + } else if ( "IsNew".equals(propertyChangeEvent.PropertyName) ){ + calling[IS_NEW] = callPos++; + } else if ( "RowCount".equals(propertyChangeEvent.PropertyName) ){ + calling[ROW_COUNT] = callPos++; + } else if ( "IsRowCountFinal".equals(propertyChangeEvent.PropertyName) ){ + calling[IS_ROW_COUNT_FINAL] = callPos++; + } + } + +} diff --git a/dbaccess/qa/complex/dbaccess/SingleSelectQueryComposer.java b/dbaccess/qa/complex/dbaccess/SingleSelectQueryComposer.java new file mode 100644 index 0000000000..656b44d00c --- /dev/null +++ b/dbaccess/qa/complex/dbaccess/SingleSelectQueryComposer.java @@ -0,0 +1,351 @@ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ +package complex.dbaccess; + +import com.sun.star.beans.PropertyState; +import com.sun.star.sdb.SQLFilterOperator; +import com.sun.star.beans.PropertyAttribute; +import com.sun.star.beans.XPropertySet; +import com.sun.star.beans.XPropertyContainer; +import com.sun.star.beans.NamedValue; +import com.sun.star.container.XNameAccess; +import com.sun.star.sdbcx.XTablesSupplier; +import com.sun.star.sdb.XParametersSupplier; +import com.sun.star.beans.PropertyValue; +import com.sun.star.sdbcx.XColumnsSupplier; +import com.sun.star.container.XIndexAccess; +import com.sun.star.sdb.CommandType; +import com.sun.star.sdb.XSingleSelectQueryComposer; +import com.sun.star.uno.UnoRuntime; +import com.sun.star.sdbc.DataType; +import com.sun.star.sdbc.SQLException; + +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; + + +// ---------- junit imports ----------------- +import org.junit.Test; + +import static org.junit.Assert.*; + + +public class SingleSelectQueryComposer extends CRMBasedTestCase +{ + + private XSingleSelectQueryComposer m_composer = null; + private static final String COMPLEXFILTER = "( \"ID\" = 1 AND \"Postal\" = '4' )" + + " OR ( \"ID\" = 2 AND \"Postal\" = '5' )" + + " OR ( \"ID\" = 3 AND \"Postal\" = '6' AND \"Address\" = '7' )" + + " OR ( \"Address\" = '8' )" + + " OR ( \"Postal\" = '9' )" + + " OR ( NOW( ) = {d '2010-01-01' } )"; + private static final String INNERPRODUCTSQUERY = "products (inner)"; + + + private void createQueries() throws Exception + { + m_database.getDatabase().getDataSource().createQuery(INNERPRODUCTSQUERY, "SELECT * FROM \"products\""); + } + + + @Override + protected void createTestCase() throws Exception + { + super.createTestCase(); + + createQueries(); + + m_composer = createQueryComposer(); + } + + + private void checkAttributeAccess(String _attributeName, String _attributeValue) + { + System.out.println("setting " + _attributeName + " to " + _attributeValue); + String realValue = null; + try + { + final Class<?> composerClass = m_composer.getClass(); + final Method attributeGetter = composerClass.getMethod("get" + _attributeName, new Class[] + { + }); + final Method attributeSetter = composerClass.getMethod("set" + _attributeName, new Class[] + { + String.class + }); + + attributeSetter.invoke(m_composer, new Object[] + { + _attributeValue + }); + realValue = (String) attributeGetter.invoke(m_composer, new Object[] + { + }); + } + catch (NoSuchMethodException e) + { + } + catch (IllegalAccessException e) + { + } + catch (InvocationTargetException e) + { + } + assertTrue("set/get" + _attributeName + " not working as expected (set: " + _attributeValue + ", get: " + (realValue != null ? realValue : "null") + ")", + realValue.equals(_attributeValue)); + System.out.println(" (results in " + m_composer.getQuery() + ")"); + } + + /** tests setCommand of the composer + */ + @Test + public void testSetCommand() throws Exception + { + System.out.println("testing SingleSelectQueryComposer's setCommand"); + + final String table = "SELECT * FROM \"customers\""; + m_composer.setCommand("customers", CommandType.TABLE); + assertTrue("setCommand/getQuery TABLE inconsistent", m_composer.getQuery().equals(table)); + + m_database.getDatabase().getDataSource().createQuery("set command test", "SELECT * FROM \"orders for customer\" \"a\", \"customers\" \"b\" WHERE \"a\".\"Product Name\" = \"b\".\"Name\""); + m_composer.setCommand("set command test", CommandType.QUERY); + assertTrue("setCommand/getQuery QUERY inconsistent", m_composer.getQuery().equals(m_database.getDatabase().getDataSource().getQueryDefinition("set command test").getCommand())); + + final String sql = "SELECT * FROM \"orders for customer\" WHERE \"Product Name\" = 'test'"; + m_composer.setCommand(sql, CommandType.COMMAND); + assertTrue("setCommand/getQuery COMMAND inconsistent", m_composer.getQuery().equals(sql)); + } + + /** tests accessing attributes of the composer (order, filter, group by, having) + */ + @Test + public void testAttributes() throws Exception + { + System.out.println("testing SingleSelectQueryComposer's attributes (order, filter, group by, having)"); + + System.out.println("check setElementaryQuery"); + + final String simpleQuery2 = "SELECT * FROM \"customers\" WHERE \"Name\" = 'oranges'"; + m_composer.setElementaryQuery(simpleQuery2); + assertTrue("setElementaryQuery/getQuery inconsistent", m_composer.getQuery().equals(simpleQuery2)); + + System.out.println("check setQuery"); + final String simpleQuery = "SELECT * FROM \"customers\""; + m_composer.setQuery(simpleQuery); + assertTrue("set/getQuery inconsistent", m_composer.getQuery().equals(simpleQuery)); + + checkAttributeAccess("Filter", "\"Name\" = 'oranges'"); + checkAttributeAccess("Group", "\"City\""); + checkAttributeAccess("Order", "\"Address\""); + checkAttributeAccess("HavingClause", "\"ID\" <> 4"); + + final XIndexAccess orderColumns = m_composer.getOrderColumns(); + assertTrue("Order columns doesn't exist: \"Address\"", + orderColumns != null && orderColumns.getCount() == 1 && orderColumns.getByIndex(0) != null); + + final XIndexAccess groupColumns = m_composer.getGroupColumns(); + assertTrue("Group columns doesn't exist: \"City\"", + groupColumns != null && groupColumns.getCount() == 1 && groupColumns.getByIndex(0) != null); + + // XColumnsSupplier + final XColumnsSupplier xSelectColumns = UnoRuntime.queryInterface(XColumnsSupplier.class, m_composer); + assertTrue("no select columns, or wrong number of select columns", + xSelectColumns != null && xSelectColumns.getColumns() != null && xSelectColumns.getColumns().getElementNames().length == 6); + + // structured filter + m_composer.setQuery("SELECT \"ID\", \"Postal\", \"Address\" FROM \"customers\""); + m_composer.setFilter(COMPLEXFILTER); + final PropertyValue[][] aStructuredFilter = m_composer.getStructuredFilter(); + m_composer.setFilter(""); + m_composer.setStructuredFilter(aStructuredFilter); + if (!m_composer.getFilter().equals(COMPLEXFILTER)) + { + System.out.println(COMPLEXFILTER); + System.out.println(m_composer.getFilter()); + } + assertTrue("Structured Filter not identical", m_composer.getFilter().equals(COMPLEXFILTER)); + + // structured having clause + m_composer.setHavingClause(COMPLEXFILTER); + final PropertyValue[][] aStructuredHaving = m_composer.getStructuredHavingClause(); + m_composer.setHavingClause(""); + m_composer.setStructuredHavingClause(aStructuredHaving); + assertTrue("Structured Having Clause not identical", m_composer.getHavingClause().equals(COMPLEXFILTER)); + } + + /** test various sub query related features ("queries in queries") + */ + @Test + public void testSubQueries() throws Exception + { + m_composer.setQuery("SELECT * from \"" + INNERPRODUCTSQUERY + "\""); + final XTablesSupplier suppTables = UnoRuntime.queryInterface(XTablesSupplier.class, m_composer); + final XNameAccess tables = suppTables.getTables(); + assertTrue("a simple SELECT * FROM <query> could not be parsed", + tables != null && tables.hasByName(INNERPRODUCTSQUERY)); + + final String sInnerCommand = m_database.getDatabase().getDataSource().getQueryDefinition(INNERPRODUCTSQUERY).getCommand(); + final String sExecutableQuery = m_composer.getQueryWithSubstitution(); + assertTrue("simple query containing a sub query improperly parsed to SDBC level statement: \n1. " + sExecutableQuery + "\n2. " + "SELECT * FROM ( " + sInnerCommand + " ) AS \"" + INNERPRODUCTSQUERY + "\"", + sExecutableQuery.equals("SELECT * FROM ( " + sInnerCommand + " ) AS \"" + INNERPRODUCTSQUERY + "\"")); + } + + /** tests the XParametersSupplier functionality + */ + @Test + public void testParameters() throws Exception + { + // "orders for customers" is a query with a named parameter (based on another query) + m_database.getDatabase().getDataSource().createQuery("orders for customer", "SELECT * FROM \"all orders\" WHERE \"Customer Name\" LIKE :cname"); + // "orders for customer and product" is query based on "orders for customers", adding an additional, + // anonymous parameter + m_database.getDatabase().getDataSource().createQuery("orders for customer and product", "SELECT * FROM \"orders for customer\" WHERE \"Product Name\" LIKE ?"); + + m_composer.setQuery(m_database.getDatabase().getDataSource().getQueryDefinition("orders for customer and product").getCommand()); + final XParametersSupplier suppParams = UnoRuntime.queryInterface(XParametersSupplier.class, m_composer); + final XIndexAccess parameters = suppParams.getParameters(); + + final String expectedParamNames[] = + + { + "cname", + "Product Name" + }; + + final int paramCount = parameters.getCount(); + assertTrue("composer did find wrong number of parameters in the nested queries.", + paramCount == expectedParamNames.length); + + for (int i = 0; i < paramCount; ++i) + { + final XPropertySet parameter = UnoRuntime.queryInterface(XPropertySet.class, parameters.getByIndex(i)); + final String paramName = (String) parameter.getPropertyValue("Name"); + assertTrue("wrong parameter name at position " + (i + 1) + " (expected: " + expectedParamNames[i] + ", found: " + paramName + ")", + paramName.equals(expectedParamNames[i])); + + } + } + + @Test + public void testConditionByColumn() throws Exception + { + m_composer.setQuery("SELECT * FROM \"customers\""); + + final Object initArgs[] = + + { + new NamedValue("AutomaticAddition", Boolean.TRUE) + }; + final String serviceName = "com.sun.star.beans.PropertyBag"; + final XPropertyContainer filter = UnoRuntime.queryInterface(XPropertyContainer.class, getMSF().createInstanceWithArguments(serviceName, initArgs)); + filter.addProperty("Name", PropertyAttribute.MAYBEVOID, "Comment"); + filter.addProperty("RealName", PropertyAttribute.MAYBEVOID, "Comment"); + filter.addProperty("TableName", PropertyAttribute.MAYBEVOID, "customers"); + filter.addProperty("Value", PropertyAttribute.MAYBEVOID, "Good one."); + filter.addProperty("Type", PropertyAttribute.MAYBEVOID, Integer.valueOf(DataType.LONGVARCHAR)); + final XPropertySet column = UnoRuntime.queryInterface(XPropertySet.class, filter); + + m_composer.appendFilterByColumn(column, true, SQLFilterOperator.LIKE); + assertTrue("At least one row should exist", m_database.getConnection().createStatement().executeQuery(m_composer.getQuery()).next()); + } + + private void impl_testDisjunctiveNormalForm(String _query, PropertyValue[][] _expectedDNF) throws SQLException + { + m_composer.setQuery(_query); + + final PropertyValue[][] disjunctiveNormalForm = m_composer.getStructuredFilter(); + + assertEquals("DNF: wrong number of rows", _expectedDNF.length, disjunctiveNormalForm.length); + for (int i = 0; i < _expectedDNF.length; ++i) + { + assertEquals("DNF: wrong number of columns in row " + i, _expectedDNF[i].length, disjunctiveNormalForm[i].length); + for (int j = 0; j < _expectedDNF[i].length; ++j) + { + assertEquals("DNF: wrong content in column " + j + ", row " + i, + _expectedDNF[i][j].Name, disjunctiveNormalForm[i][j].Name); + } + } + } + + /** tests the disjunctive normal form functionality, aka the structured filter, + * of the composer + */ + @Test + public void testDisjunctiveNormalForm() throws Exception + { + // a simple case: WHERE clause simply is a combination of predicates knitted with AND + String query = + "SELECT \"customers\".\"Name\", " + + "\"customers\".\"Address\", " + + "\"customers\".\"City\", " + + "\"customers\".\"Postal\", " + + "\"products\".\"Name\" " + + "FROM \"orders\", \"customers\", \"orders_details\", \"products\" " + + "WHERE ( \"orders\".\"CustomerID\" = \"customers\".\"ID\" " + + "AND \"orders_details\".\"OrderID\" = \"orders\".\"ID\" " + + "AND \"orders_details\".\"ProductID\" = \"products\".\"ID\" " + + ") "; + + impl_testDisjunctiveNormalForm(query, new PropertyValue[][] + { + new PropertyValue[] + { + new PropertyValue("CustomerID", SQLFilterOperator.EQUAL, "\"customers\".\"ID\"", PropertyState.DIRECT_VALUE), + new PropertyValue("OrderID", SQLFilterOperator.EQUAL, "\"orders\".\"ID\"", PropertyState.DIRECT_VALUE), + new PropertyValue("ProductID", SQLFilterOperator.EQUAL, "\"products\".\"ID\"", PropertyState.DIRECT_VALUE) + } + }); + + // somewhat more challenging: One of the conjunction terms is a disjunction itself + query = + "SELECT \"customers\".\"Name\", " + + "\"customers\".\"Address\", " + + "\"customers\".\"City\", " + + "\"customers\".\"Postal\", " + + "\"products\".\"Name\" " + + "FROM \"orders\", \"customers\", \"orders_details\", \"products\" " + + "WHERE ( \"orders\".\"CustomerID\" = \"customers\".\"ID\" " + + "AND \"orders_details\".\"OrderID\" = \"orders\".\"ID\" " + + "AND \"orders_details\".\"ProductID\" = \"products\".\"ID\" " + + ") " + + "AND " + + "( \"products\".\"Name\" = 'Apples' " + + "OR \"products\".\"ID\" = 2 " + + ")"; + + impl_testDisjunctiveNormalForm(query, new PropertyValue[][] + { + new PropertyValue[] + { + new PropertyValue("CustomerID", SQLFilterOperator.EQUAL, "\"customers\".\"ID\"", PropertyState.DIRECT_VALUE), + new PropertyValue("OrderID", SQLFilterOperator.EQUAL, "\"orders\".\"ID\"", PropertyState.DIRECT_VALUE), + new PropertyValue("ProductID", SQLFilterOperator.EQUAL, "\"products\".\"ID\"", PropertyState.DIRECT_VALUE), + new PropertyValue("Name", SQLFilterOperator.EQUAL, "Apples", PropertyState.DIRECT_VALUE) + }, + new PropertyValue[] + { + new PropertyValue("CustomerID", SQLFilterOperator.EQUAL, "\"customers\".\"ID\"", PropertyState.DIRECT_VALUE), + new PropertyValue("OrderID", SQLFilterOperator.EQUAL, "\"orders\".\"ID\"", PropertyState.DIRECT_VALUE), + new PropertyValue("ProductID", SQLFilterOperator.EQUAL, "\"products\".\"ID\"", PropertyState.DIRECT_VALUE), + new PropertyValue("ID", SQLFilterOperator.EQUAL, Integer.valueOf(2), PropertyState.DIRECT_VALUE) + } + }); + + } +} diff --git a/dbaccess/qa/complex/dbaccess/TestCase.java b/dbaccess/qa/complex/dbaccess/TestCase.java new file mode 100644 index 0000000000..8c284ae51f --- /dev/null +++ b/dbaccess/qa/complex/dbaccess/TestCase.java @@ -0,0 +1,232 @@ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ +package complex.dbaccess; + +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import com.sun.star.beans.PropertyValue; +import com.sun.star.frame.XComponentLoader; +import com.sun.star.frame.XModel; +import com.sun.star.lang.XMultiServiceFactory; +import com.sun.star.uno.UnoRuntime; +import com.sun.star.uno.XComponentContext; +import helper.FileTools; +import java.io.File; +import java.io.IOException; +import java.net.URI; +import java.net.URISyntaxException; + +// ---------- junit imports ----------------- +import org.junit.AfterClass; +import org.junit.BeforeClass; +import org.openoffice.test.OfficeConnection; +import static org.junit.Assert.*; + + + +public abstract class TestCase +{ + + protected final XComponentContext getComponentContext() + { + return connection.getComponentContext(); + } + + + public void before() throws java.lang.Exception + { + } + + + public void after() throws java.lang.Exception + { + } + + + /** returns the URL of a temporary file which can be used during the test. + * + * The file will be deleted when the process exits + * @return the URL of a temporary file + */ + protected final String createTempFileURL() throws IOException + { + final File documentFile = java.io.File.createTempFile( "dbaccess_test", ".odb" ).getAbsoluteFile(); + if ( documentFile.exists() ) + { + documentFile.delete(); + } + return FileHelper.getOOoCompatibleFileURL( documentFile.toURI().toURL().toString() ); + } + + + /** + * copies the file given by URL to a temporary file + * @return + * the URL of the new file + */ + protected final String copyToTempFile( String _sourceURL ) throws IOException + { + final String targetURL = createTempFileURL(); + try + { + FileTools.copyFile( new File( new URI( _sourceURL ) ), new File( new URI( targetURL ) ) ); + } + catch ( URISyntaxException e ) { } + + return FileHelper.getOOoCompatibleFileURL( targetURL ); + } + + + protected final XModel loadDocument( final String _docURL ) throws Exception + { + final XComponentLoader loader = UnoRuntime.queryInterface( XComponentLoader.class, + getMSF().createInstance( "com.sun.star.frame.Desktop" ) ); + return UnoRuntime.queryInterface( XModel.class, + loader.loadComponentFromURL( _docURL, "_blank", 0, new PropertyValue[] {} ) ); + } + + + /** invokes a given method on a given object, and assures a certain exception is caught + * @param _message + * is the message to print when the check fails + * @param _object + * is the object to invoke the method on + * @param _methodName + * is the name of the method to invoke + * @param _methodArgs + * are the arguments to pass to the method. + * @param _argClasses + * are the classes to assume for the arguments of the methods + * @param _expectedExceptionClass + * is the class of the exception to be caught. If this is null, + * it means that <em>no</em> exception must be throw by invoking the method. + */ + private void assureException( final String _message, final Object _object, final String _methodName, + final Class[] _argClasses, final Object[] _methodArgs, final Class _expectedExceptionClass ) + { + Class<?> objectClass = _object.getClass(); + + boolean noExceptionAllowed = ( _expectedExceptionClass == null ); + + boolean caughtExpected = noExceptionAllowed; + try + { + Method method = objectClass.getMethod( _methodName, _argClasses ); + method.invoke(_object, _methodArgs ); + } + catch ( InvocationTargetException e ) + { + caughtExpected = noExceptionAllowed + ? false + : ( e.getTargetException().getClass().equals( _expectedExceptionClass ) ); + } + catch( Exception e ) + { + caughtExpected = false; + } + + assertTrue( _message, caughtExpected ); + } + + /** invokes a given method on a given object, and assures a certain exception is caught + * @param _message is the message to print when the check fails + * @param _object is the object to invoke the method on + * @param _methodName is the name of the method to invoke + * @param _methodArgs are the arguments to pass to the method. Those implicitly define + * the classes of the arguments of the method which is called. + * @param _expectedExceptionClass is the class of the exception to be caught. If this is null, + * it means that <em>no</em> exception must be throw by invoking the method. + */ + private void assureException( final String _message, final Object _object, final String _methodName, + final Object[] _methodArgs, final Class _expectedExceptionClass ) + { + Class[] argClasses = new Class[ _methodArgs.length ]; + for ( int i=0; i<_methodArgs.length; ++i ) + argClasses[i] = _methodArgs[i].getClass(); + assureException( _message, _object, _methodName, argClasses, _methodArgs, _expectedExceptionClass ); + } + + /** invokes a given method on a given object, and assures a certain exception is caught + * @param _object is the object to invoke the method on + * @param _methodName is the name of the method to invoke + * @param _methodArgs are the arguments to pass to the method. Those implicitly define + * the classes of the arguments of the method which is called. + * @param _expectedExceptionClass is the class of the exception to be caught. If this is null, + * it means that <em>no</em> exception must be throw by invoking the method. + */ + private void assureException( final Object _object, final String _methodName, final Object[] _methodArgs, + final Class _expectedExceptionClass ) + { + assureException( + "did not catch the expected exception (" + + ( ( _expectedExceptionClass == null ) ? "none" : _expectedExceptionClass.getName() ) + + ") while calling " + _object.getClass().getName() + "." + _methodName, + _object, _methodName, _methodArgs, _expectedExceptionClass ); + } + + /** invokes a given method on a given object, and assures a certain exception is caught + * @param _object is the object to invoke the method on + * @param _methodName is the name of the method to invoke + * @param _methodArgs are the arguments to pass to the method + * @param _argClasses are the classes to assume for the arguments of the methods + * @param _expectedExceptionClass is the class of the exception to be caught. If this is null, + * it means that <em>no</em> exception must be throw by invoking the method. + */ + protected void assureException( final Object _object, final String _methodName, final Class[] _argClasses, + final Object[] _methodArgs, final Class _expectedExceptionClass ) + { + assureException( + "did not catch the expected exception (" + + ( ( _expectedExceptionClass == null ) ? "none" : _expectedExceptionClass.getName() ) + + ") while calling " + _object.getClass().getName() + "." + _methodName, + _object, _methodName, _argClasses, _methodArgs, _expectedExceptionClass ); + } + + + @SuppressWarnings("unchecked") + protected void assureException( Object _object, Class _unoInterfaceClass, String _methodName, Object[] _methodArgs, + Class _expectedExceptionClass ) + { + assureException( UnoRuntime.queryInterface( _unoInterfaceClass, _object ), _methodName, + _methodArgs, _expectedExceptionClass ); + } + + + protected final XMultiServiceFactory getMSF() + { + return UnoRuntime.queryInterface(XMultiServiceFactory.class, connection.getComponentContext().getServiceManager()); + } + + + // setup and close connections + @BeforeClass + public static void setUpConnection() throws Exception + { + connection.setUp(); + } + + + @AfterClass + public static void tearDownConnection() throws InterruptedException, com.sun.star.uno.Exception + { + connection.tearDown(); + } + + private static final OfficeConnection connection = new OfficeConnection(); + +} diff --git a/dbaccess/qa/complex/dbaccess/UISettings.java b/dbaccess/qa/complex/dbaccess/UISettings.java new file mode 100644 index 0000000000..273d6b7a70 --- /dev/null +++ b/dbaccess/qa/complex/dbaccess/UISettings.java @@ -0,0 +1,132 @@ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ +package complex.dbaccess; + +import com.sun.star.awt.FontSlant; +import com.sun.star.awt.TextAlign; +import com.sun.star.beans.XPropertySet; +import com.sun.star.container.XNameAccess; +import com.sun.star.form.runtime.XFormController; +import com.sun.star.frame.XController; +import com.sun.star.sdb.application.DatabaseObject; +import com.sun.star.uno.UnoRuntime; +import com.sun.star.util.XCloseable; +import connectivity.tools.CRMDatabase; + +// ---------- junit imports ----------------- +import org.junit.Test; +import static org.junit.Assert.*; + + +public class UISettings extends TestCase +{ + + /** verifies that aliases for inner queries work as expected + */ + @Test + public void checkTableFormattingPersistence() throws java.lang.Exception + { + // create, load, and connect a DB doc + CRMDatabase database = new CRMDatabase( getMSF(), true ); + + // display a table + XFormController tableViewController = UnoRuntime.queryInterface( XFormController.class, + database.loadSubComponent( DatabaseObject.TABLE, "customers" ) ); + XPropertySet tableControlModel = UnoRuntime.queryInterface( XPropertySet.class, + tableViewController.getCurrentControl().getModel() ); + + // change the table's formatting + tableControlModel.setPropertyValue( "FontName", "Andale Sans UI" ); + tableControlModel.setPropertyValue( "FontHeight", Float.valueOf( 20 ) ); + tableControlModel.setPropertyValue( "FontSlant", FontSlant.ITALIC ); + + String docURL = database.getDatabase().getModel().getURL(); + + // save close the database document + database.saveAndClose(); + + // load a copy of the document + // normally, it should be sufficient to load the same doc. However, there might be objects in the Java VM + // which are not yet freed, and which effectively hold the document alive. More precise: The document (|doc|) + // is certainly disposed, but other objects might hold a reference to one of the many other components + // around the database document, the data source, the connection, etc. As long as those objects are + // not cleaned up, the "database model impl" - the structure holding all document data - will + // stay alive, and subsequent requests to load the doc will just reuse it, without really loading it. + docURL = copyToTempFile( docURL ); + loadDocument( docURL ); + database = new CRMDatabase( getMSF(), docURL ); + + // display the table, again + tableViewController = UnoRuntime.queryInterface( XFormController.class, + database.loadSubComponent( DatabaseObject.TABLE, "customers" ) ); + tableControlModel = UnoRuntime.queryInterface( XPropertySet.class, + tableViewController.getCurrentControl().getModel() ); + + // verify the properties + assertEquals( "wrong font name", "Andale Sans UI", tableControlModel.getPropertyValue( "FontName" ) ); + assertEquals( "wrong font height", 20, ((Float)tableControlModel.getPropertyValue( "FontHeight" )).floatValue(), 0 ); + assertEquals( "wrong font slant", FontSlant.ITALIC, tableControlModel.getPropertyValue( "FontSlant" ) ); + + // close the doc + database.saveAndClose(); + } + + /** + * checks whether query columns use the settings of the underlying table column, if they do not (yet) have own + * settings + */ + @Test + public void checkTransparentQueryColumnSettings() throws java.lang.Exception + { + // create, load, and connect a DB doc + CRMDatabase database = new CRMDatabase( getMSF(), true ); + + // display a table + XController tableView = database.loadSubComponent( DatabaseObject.TABLE, "customers" ); + XFormController tableViewController = UnoRuntime.queryInterface( XFormController.class, + tableView ); + XNameAccess tableControlModel = UnoRuntime.queryInterface( XNameAccess.class, + tableViewController.getCurrentControl().getModel() ); + + // change the formatting of a table column + XPropertySet idColumn = UnoRuntime.queryInterface( XPropertySet.class, tableControlModel.getByName( "ID" ) ); + assertTrue( "precondition not met: column already centered", + ((Short)idColumn.getPropertyValue( "Align" )).shortValue() != TextAlign.CENTER ); + idColumn.setPropertyValue( "Align", TextAlign.CENTER ); + + // close the table data view + XCloseable closeSubComponent = UnoRuntime.queryInterface( XCloseable.class, tableView.getFrame() ); + closeSubComponent.close( true ); + + // create a query based on that column + database.getDatabase().getDataSource().createQuery( "q_customers", "SELECT * FROM \"customers\"" ); + + // load this query, and verify the table column settings was propagated to the query column + XFormController queryViewController = UnoRuntime.queryInterface( XFormController.class, + database.loadSubComponent( DatabaseObject.QUERY, "q_customers" ) ); + tableControlModel = UnoRuntime.queryInterface( XNameAccess.class, + queryViewController.getCurrentControl().getModel() ); + idColumn = UnoRuntime.queryInterface( XPropertySet.class, tableControlModel.getByName( "ID" ) ); + + assertTrue( "table column alignment was not propagated to the query column", + ((Short)idColumn.getPropertyValue( "Align" )).shortValue() == TextAlign.CENTER ); + + // save close the database document + database.saveAndClose(); + } +} diff --git a/dbaccess/qa/complex/dbaccess/makefile.mk b/dbaccess/qa/complex/dbaccess/makefile.mk new file mode 100644 index 0000000000..862a0ffc96 --- /dev/null +++ b/dbaccess/qa/complex/dbaccess/makefile.mk @@ -0,0 +1,93 @@ +# +# 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 . +# + +.IF "$(OOO_JUNIT_JAR)" == "" +nothing .PHONY: + @echo ----------------------------------------------------- + @echo - JUnit not available, not building anything + @echo ----------------------------------------------------- +.ELSE + +PRJ = ../../.. +PRJNAME = dbaccess +TARGET = qa_complex_dbaccess +PACKAGE = complex/dbaccess + +# --- Settings ----------------------------------------------------- +.INCLUDE: settings.mk + +#----- compile .java files ----------------------------------------- + +JARFILES = OOoRunner.jar ridl.jar test.jar juh.jar unoil.jar ConnectivityTools.jar +EXTRAJARFILES = $(OOO_JUNIT_JAR) + +#----- create a jar from compiled files ---------------------------- + +JARTARGET = $(TARGET).jar + +#----- Java files -------------------------------------------------- + +# here store only Files which contain a @Test +JAVATESTFILES = \ + ApplicationController.java \ + Beamer.java \ + DataSource.java \ + DatabaseDocument.java \ + Parser.java \ + PropertyBag.java \ + Query.java \ + QueryInQuery.java \ + RowSet.java \ + SingleSelectQueryComposer.java \ + UISettings.java \ + CopyTableWizard.java \ + +# put here all other files +JAVAFILES = $(JAVATESTFILES) \ + CRMBasedTestCase.java \ + CopyTableInterActionHandler.java \ + DatabaseApplication.java \ + FileHelper.java \ + RowSetEventListener.java \ + TestCase.java \ + + +# Sample how to debug +# JAVAIFLAGS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,address=9003,suspend=y + +# --- Targets ------------------------------------------------------ + +.INCLUDE: target.mk + +ALL : ALLTAR + +# --- subsequent tests --------------------------------------------- + +.IF "$(OOO_SUBSEQUENT_TESTS)" != "" + +.INCLUDE: installationtest.mk + +ALLTAR : javatest + + # Sample how to debug + # JAVAIFLAGS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,address=9003,suspend=y + +.END # "$(OOO_SUBSEQUENT_TESTS)" == "" + +.END # ELSE "$(OOO_JUNIT_JAR)" != "" + |