1 /************************************************************** 2 * 3 * Licensed to the Apache Software Foundation (ASF) under one 4 * or more contributor license agreements. See the NOTICE file 5 * distributed with this work for additional information 6 * regarding copyright ownership. The ASF licenses this file 7 * to you under the Apache License, Version 2.0 (the 8 * "License"); you may not use this file except in compliance 9 * with the License. You may obtain a copy of the License at 10 * 11 * http://www.apache.org/licenses/LICENSE-2.0 12 * 13 * Unless required by applicable law or agreed to in writing, 14 * software distributed under the License is distributed on an 15 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 16 * KIND, either express or implied. See the License for the 17 * specific language governing permissions and limitations 18 * under the License. 19 * 20 *************************************************************/ 21 22 23 24 package integration.forms; 25 26 import com.sun.star.beans.NamedValue; 27 import com.sun.star.beans.XPropertySet; 28 import com.sun.star.container.XIndexContainer; 29 import java.lang.reflect.Method; 30 31 import com.sun.star.uno.UnoRuntime; 32 import com.sun.star.lang.XMultiServiceFactory; 33 import com.sun.star.container.XNameContainer; 34 import com.sun.star.embed.XComponentSupplier; 35 import com.sun.star.form.XGridColumnFactory; 36 import com.sun.star.form.XGridFieldDataSupplier; 37 import com.sun.star.form.XLoadable; 38 import com.sun.star.lang.XComponent; 39 import com.sun.star.sdb.CommandType; 40 import com.sun.star.sdb.XFormDocumentsSupplier; 41 import com.sun.star.sdbc.SQLException; 42 import com.sun.star.sdbc.XColumnLocate; 43 import com.sun.star.ucb.Command; 44 import com.sun.star.ucb.OpenMode; 45 import com.sun.star.ucb.XCommandProcessor; 46 import com.sun.star.uno.Type; 47 import com.sun.star.util.XModifiable; 48 import connectivity.tools.CRMDatabase; 49 import connectivity.tools.HsqlColumnDescriptor; 50 import connectivity.tools.HsqlDatabase; 51 import connectivity.tools.HsqlTableDescriptor; 52 import org.openoffice.complex.forms.tools.ResultSet; 53 54 import org.junit.After; 55 import org.junit.AfterClass; 56 import org.junit.Before; 57 import org.junit.BeforeClass; 58 import org.junit.Test; 59 import static org.junit.Assert.*; 60 import org.openoffice.test.OfficeConnection; 61 62 63 public class MasterDetailForms implements com.sun.star.form.XLoadListener 64 { 65 private static final OfficeConnection officeConnection = new OfficeConnection(); 66 private XMultiServiceFactory m_orb; 67 68 private XPropertySet m_masterForm; 69 private XPropertySet m_detailForm; 70 private ResultSet m_masterResult; 71 private ResultSet m_detailResult; 72 73 final private Object m_waitForLoad = new Object(); 74 private boolean m_loaded = false; 75 76 @BeforeClass beforeClass()77 public static void beforeClass() throws Exception 78 { 79 officeConnection.setUp(); 80 } 81 82 @AfterClass afterClass()83 public static void afterClass() throws Exception 84 { 85 officeConnection.tearDown(); 86 } 87 88 89 /** Creates a new instance of MasterDetailForms */ MasterDetailForms()90 public MasterDetailForms() 91 { 92 } 93 94 /* ------------------------------------------------------------------ */ getTestMethodNames()95 public String[] getTestMethodNames() 96 { 97 return new String[] { 98 "checkMultipleKeys", 99 "checkDetailFormDefaults" 100 }; 101 } 102 103 /* ------------------------------------------------------------------ */ 104 @Before before()105 public void before() 106 { 107 m_orb = UnoRuntime.queryInterface(XMultiServiceFactory.class, officeConnection.getComponentContext().getServiceManager()); 108 } 109 110 /* ------------------------------------------------------------------ */ getTestObjectName()111 public String getTestObjectName() 112 { 113 return "Form Control Spreadsheet Cell Binding Test"; 114 } 115 116 /* ------------------------------------------------------------------ */ 117 /** creates the table structure needed for the test 118 */ impl_createTableStructure( final HsqlDatabase _databaseDocument )119 private void impl_createTableStructure( final HsqlDatabase _databaseDocument ) throws SQLException 120 { 121 HsqlColumnDescriptor[] masterColumns = { 122 new HsqlColumnDescriptor( "ID1", "INTEGER", HsqlColumnDescriptor.PRIMARY ), 123 new HsqlColumnDescriptor( "ID2", "INTEGER", HsqlColumnDescriptor.PRIMARY ), 124 new HsqlColumnDescriptor( "value", "VARCHAR(50)" ), 125 }; 126 HsqlColumnDescriptor[] detailColumns = { 127 new HsqlColumnDescriptor( "ID", "INTEGER", HsqlColumnDescriptor.PRIMARY ), 128 new HsqlColumnDescriptor( "FK_ID1", "INTEGER", HsqlColumnDescriptor.REQUIRED, "master", "ID1" ), 129 new HsqlColumnDescriptor( "FK_ID2", "INTEGER", HsqlColumnDescriptor.REQUIRED, "master", "ID2" ), 130 new HsqlColumnDescriptor( "name", "VARCHAR(50)" ), 131 }; 132 _databaseDocument.createTable( new HsqlTableDescriptor( "master", masterColumns ) ); 133 _databaseDocument.createTable( new HsqlTableDescriptor( "detail", detailColumns ) ); 134 135 _databaseDocument.executeSQL( "INSERT INTO \"master\" VALUES ( 1, 1, 'First Record' )" ); 136 _databaseDocument.executeSQL( "INSERT INTO \"master\" VALUES ( 1, 2, 'Second Record' )" ); 137 _databaseDocument.executeSQL( "INSERT INTO \"detail\" VALUES ( 1, 1, 1, 'record 1.1 (1)')"); 138 _databaseDocument.executeSQL( "INSERT INTO \"detail\" VALUES ( 2, 1, 1, 'record 1.1 (2)')"); 139 _databaseDocument.executeSQL( "INSERT INTO \"detail\" VALUES ( 3, 1, 2, 'record 1.2 (1)')"); 140 141 _databaseDocument.defaultConnection().refreshTables(); 142 } 143 144 /* ------------------------------------------------------------------ */ impl_createForms( final HsqlDatabase _databaseDocument )145 private void impl_createForms( final HsqlDatabase _databaseDocument ) throws com.sun.star.uno.Exception 146 { 147 m_masterForm = dbfTools.queryPropertySet( m_orb.createInstance( "com.sun.star.form.component.DataForm" ) ); 148 m_masterForm.setPropertyValue( "ActiveConnection", _databaseDocument.defaultConnection().getXConnection() ); 149 m_masterForm.setPropertyValue( "CommandType", new Integer( com.sun.star.sdb.CommandType.TABLE ) ); 150 m_masterForm.setPropertyValue( "Command", "master" ); 151 152 m_masterResult = new ResultSet( m_masterForm ); 153 154 m_detailForm = dbfTools.queryPropertySet( m_orb.createInstance( "com.sun.star.form.component.DataForm" ) ); 155 m_detailForm.setPropertyValue( "ActiveConnection", _databaseDocument.defaultConnection().getXConnection() ); 156 m_detailForm.setPropertyValue( "CommandType", new Integer( com.sun.star.sdb.CommandType.TABLE ) ); 157 m_detailForm.setPropertyValue( "Command", "detail" ); 158 159 m_detailResult = new ResultSet( m_detailForm ); 160 161 XNameContainer masterContainer = UnoRuntime.queryInterface( XNameContainer.class, m_masterForm ); 162 masterContainer.insertByName( "slave", m_detailForm ); 163 } 164 165 /* ------------------------------------------------------------------ */ 166 /** checks if master-detail relationships including multiple keys work 167 */ 168 @Test checkMultipleKeys()169 public void checkMultipleKeys() throws com.sun.star.uno.Exception, java.lang.Exception 170 { 171 HsqlDatabase databaseDocument = null; 172 try 173 { 174 databaseDocument = new HsqlDatabase( m_orb ); 175 impl_createTableStructure( databaseDocument ); 176 impl_createForms( databaseDocument ); 177 178 m_detailForm.setPropertyValue( "MasterFields", new String[] { "ID1", "ID2" } ); 179 m_detailForm.setPropertyValue( "DetailFields", new String[] { "FK_ID1", "FK_ID2" } ); 180 181 XLoadable loadMaster = UnoRuntime.queryInterface( XLoadable.class, m_masterForm ); 182 XLoadable loadDetail = UnoRuntime.queryInterface( XLoadable.class, m_detailForm ); 183 loadDetail.addLoadListener( this ); 184 185 // wait until the detail form is loaded 186 operateMasterAndWaitForDetailForm( loadMaster.getClass().getMethod( "load", new Class[] {} ), loadMaster, new Object[] { } ); 187 188 // okay, now the master form should be on the first record 189 assertTrue( "wrong form state after loading (ID1)", m_masterResult.getInt(1) == 1 ); 190 assertTrue( "wrong form state after loading (ID2)", m_masterResult.getInt(2) == 1 ); 191 assertTrue( "wrong form state after loading (value)", m_masterResult.getString(3).equals( "First Record" ) ); 192 193 // the "XResultSet.next" method 194 Method methodNext = m_masterResult.getClass().getMethod( "next" , new Class[] {} ); 195 196 // the values in the linked fields should be identical 197 int expectedDetailRowCounts[] = { 2, 1 }; 198 do 199 { 200 verifyColumnValueIdentity( "ID1", "FK_ID1" ); 201 verifyColumnValueIdentity( "ID2", "FK_ID2" ); 202 203 m_detailResult.last(); 204 int masterPos = m_masterResult.getRow(); 205 assertTrue( "wrong number of records in detail form, for master form at pos " + masterPos, 206 ((Integer)m_detailForm.getPropertyValue( "RowCount" )).intValue() == expectedDetailRowCounts[ masterPos - 1 ] ); 207 208 operateMasterAndWaitForDetailForm( methodNext, m_masterResult, new Object[] {} ); 209 } 210 while ( !m_masterResult.isAfterLast() ); 211 assertTrue( "wrong number of records in master form", 2 == ((Integer)m_masterForm.getPropertyValue( "RowCount" )).intValue() ); 212 } 213 finally 214 { 215 if ( databaseDocument != null ) 216 databaseDocument.closeAndDelete(); 217 impl_cleanUpStep(); 218 } 219 } 220 221 /* ------------------------------------------------------------------ */ impl_cleanUpStep()222 private final void impl_cleanUpStep() 223 { 224 if ( m_masterForm != null ) 225 dbfTools.disposeComponent( m_masterForm ); 226 if ( m_detailForm != null ) 227 dbfTools.disposeComponent( m_detailForm ); 228 m_masterForm = m_detailForm = null; 229 } 230 231 /* ------------------------------------------------------------------ */ 232 /** checks whether default values in detail forms work as expected. 233 * 234 * Effectively, this test case verifies the issues #i106574# and #i105235# did not creep back in. 235 */ 236 @Test checkDetailFormDefaults()237 public void checkDetailFormDefaults() throws Exception 238 { 239 CRMDatabase database = null; 240 XCommandProcessor subComponentCommands = null; 241 try 242 { 243 // create our standard CRM database document 244 database = new CRMDatabase( m_orb, true ); 245 246 // create a form document therein 247 XFormDocumentsSupplier formDocSupp = UnoRuntime.queryInterface( XFormDocumentsSupplier.class, database.getDatabase().getModel() ); 248 XMultiServiceFactory formFactory = UnoRuntime.queryInterface( XMultiServiceFactory.class, formDocSupp.getFormDocuments() ); 249 NamedValue[] loadArgs = new NamedValue[] { 250 new NamedValue( "ActiveConnection", database.getConnection().getXConnection() ), 251 new NamedValue( "MediaType", "application/vnd.oasis.opendocument.text" ) 252 }; 253 254 subComponentCommands = UnoRuntime.queryInterface( 255 XCommandProcessor.class, 256 formFactory.createInstanceWithArguments( "com.sun.star.sdb.DocumentDefinition", loadArgs ) ); 257 Command command = new Command(); 258 command.Name = "openDesign"; 259 command.Argument = new Short( OpenMode.DOCUMENT ); 260 261 DocumentHelper subDocument = new DocumentHelper( m_orb, 262 UnoRuntime.queryInterface( XComponent.class, 263 subComponentCommands.execute( command, subComponentCommands.createCommandIdentifier(), null ) 264 ) 265 ); 266 FormLayer formLayer = new FormLayer( subDocument ); 267 XPropertySet controlModel = formLayer.insertControlLine( "DatabaseNumericField", "ID", "", 10 ); 268 formLayer.insertControlLine( "DatabaseTextField", "Name", "", 20 ); 269 formLayer.insertControlLine( "DatabaseTextField", "Description", "", 30 ); 270 271 m_masterForm = (XPropertySet)dbfTools.getParent( controlModel, XPropertySet.class ); 272 m_masterForm.setPropertyValue( "Command", "categories" ); 273 m_masterForm.setPropertyValue( "CommandType", new Integer( CommandType.TABLE ) ); 274 275 // create a detail form 276 m_detailForm = UnoRuntime.queryInterface( XPropertySet.class, subDocument.createSubForm( m_masterForm, "products" ) ); 277 m_detailForm.setPropertyValue( "Command", "SELECT \"ID\", \"Name\", \"CategoryID\" FROM \"products\"" ); 278 m_detailForm.setPropertyValue( "CommandType", new Integer( CommandType.COMMAND ) ); 279 m_detailForm.setPropertyValue( "MasterFields", new String[] { "ID" } ); 280 m_detailForm.setPropertyValue( "DetailFields", new String[] { "CategoryID" } ); 281 282 // create a grid control in the detail form, with some columns 283 XPropertySet gridControlModel = formLayer.createControlAndShape( "GridControl", 20, 40, 130, 50, m_detailForm ); 284 gridControlModel.setPropertyValue( "Name", "product list" ); 285 XIndexContainer gridColumns = UnoRuntime.queryInterface( XIndexContainer.class, gridControlModel ); 286 impl_createGridColumn( gridColumns, "TextField", "ID" ); 287 XPropertySet nameColumn = impl_createGridColumn( gridColumns, "TextField", "Name" ); 288 nameColumn.setPropertyValue( "Width", new Integer( 600 ) ); // 6 cm 289 nameColumn.setPropertyValue( "DefaultText", "default text" ); 290 291 // go live 292 m_masterResult = new ResultSet( m_masterForm ); 293 m_detailResult = new ResultSet( m_detailForm ); 294 295 XLoadable loadDetail = UnoRuntime.queryInterface( XLoadable.class, m_detailForm ); 296 loadDetail.addLoadListener( this ); 297 298 subDocument.getCurrentView().toggleFormDesignMode(); 299 impl_waitForLoadedEvent(); 300 301 // now that we set up this, do the actual tests 302 // First, http://www.openoffice.org/issues/show_bug.cgi?id=105235 described the problem 303 // that default values in the sub form didn't work when the master form was navigated to a row 304 // for which no detail records were present, and the default of the column/control is the same 305 // as the last known value. 306 307 // so, take the current value of the "Name" column, and set it as default value ... 308 String defaultValue = m_detailResult.getString( 2 ); 309 nameColumn.setPropertyValue( "DefaultText", defaultValue ); 310 // ... then move to the second main form row ... 311 m_masterResult.absolute( 2 ); 312 impl_waitForLoadedEvent(); 313 // ... which should result in an empty sub form ... 314 assertTrue( "test precondition not met: The second master form record is expected to have no detail records, " + 315 "else the test becomes meaningless", impl_isNewRecord( m_detailForm ) ); 316 // ... and in the "Name" column having the proper text 317 String actualValue = (String)nameColumn.getPropertyValue( "Text" ); 318 assertEquals( "#i105235#: default value in sub form not working (not propagated to column model)", defaultValue, actualValue ); 319 // However, checking the column model's value alone is not enough - we need to ensure it is properly 320 // propagated to the control. 321 XGridFieldDataSupplier gridData = (XGridFieldDataSupplier)subDocument.getCurrentView().getControl( 322 gridControlModel, XGridFieldDataSupplier.class ); 323 actualValue = (String)(gridData.queryFieldData( 0, Type.STRING )[1]); 324 assertEquals( "#i105235#: default value in sub form not working (not propagated to column)", defaultValue, actualValue ); 325 } 326 finally 327 { 328 if ( subComponentCommands != null ) 329 { 330 XComponentSupplier componentSupplier = UnoRuntime.queryInterface( XComponentSupplier.class, subComponentCommands ); 331 XModifiable modifySubComponent = UnoRuntime.queryInterface( XModifiable.class, componentSupplier.getComponent() ); 332 modifySubComponent.setModified( false ); 333 Command command = new Command(); 334 command.Name = "close"; 335 subComponentCommands.execute( command, subComponentCommands.createCommandIdentifier(), null ); 336 } 337 338 if ( database != null ) 339 database.saveAndClose(); 340 341 impl_cleanUpStep(); 342 } 343 } 344 345 /* ------------------------------------------------------------------ */ impl_isNewRecord( final XPropertySet _rowSet )346 private boolean impl_isNewRecord( final XPropertySet _rowSet ) 347 { 348 boolean isNew = false; 349 try 350 { 351 isNew = ((Boolean)_rowSet.getPropertyValue( "IsNew" )).booleanValue(); 352 } 353 catch ( Exception ex ) 354 { 355 fail( "obtaining the IsNew property failed" ); 356 } 357 return isNew; 358 } 359 360 /* ------------------------------------------------------------------ */ impl_createGridColumn( final XIndexContainer _gridModel, final String _columnType, final String _boundField )361 private XPropertySet impl_createGridColumn( final XIndexContainer _gridModel, final String _columnType, final String _boundField ) throws Exception 362 { 363 final XGridColumnFactory columnFactory = UnoRuntime.queryInterface( XGridColumnFactory.class, _gridModel ); 364 XPropertySet column = columnFactory.createColumn( _columnType ); 365 column.setPropertyValue( "DataField", _boundField ); 366 column.setPropertyValue( "Name", _boundField ); 367 column.setPropertyValue( "Label", _boundField ); 368 _gridModel.insertByIndex( _gridModel.getCount(), column ); 369 return column; 370 } 371 372 /* ------------------------------------------------------------------ */ 373 /** executes an operation on the master, and waits until the detail form has been (re)loaded afterwards 374 */ operateMasterAndWaitForDetailForm( Method _masterMethod, Object _masterInterface, Object[] _methodParameters )375 private void operateMasterAndWaitForDetailForm( Method _masterMethod, Object _masterInterface, Object[] _methodParameters ) throws SQLException 376 { 377 Object result; 378 try 379 { 380 result = _masterMethod.invoke( _masterInterface, _methodParameters ); 381 } 382 catch( java.lang.Exception e ) 383 { 384 throw new SQLException( "invoking " + _masterMethod.getName() + " failed",new Object(), "", 0, new Object() ); 385 } 386 387 if ( _masterMethod.getReturnType().getName().equals( "boolean" ) ) 388 if ( !((Boolean)result).booleanValue() ) 389 return; 390 391 impl_waitForLoadedEvent(); 392 } 393 impl_waitForLoadedEvent()394 private void impl_waitForLoadedEvent() 395 { 396 synchronized( m_waitForLoad ) 397 { 398 while ( !m_loaded ) 399 { 400 try { m_waitForLoad.wait(); } 401 catch( java.lang.InterruptedException e ) { } 402 } 403 // reset the flag for the next time 404 m_loaded = false; 405 } 406 } 407 408 /** assures that the (integer) values in the given columns of our master and detail forms are identical 409 */ verifyColumnValueIdentity( final String masterColName, final String detailColName )410 private void verifyColumnValueIdentity( final String masterColName, final String detailColName ) throws SQLException 411 { 412 XColumnLocate locateMasterCols = UnoRuntime.queryInterface( XColumnLocate.class, m_masterForm ); 413 XColumnLocate locateDetailCols = UnoRuntime.queryInterface( XColumnLocate.class, m_detailForm ); 414 415 int masterValue = m_masterResult.getInt( locateMasterCols.findColumn( masterColName ) ); 416 int detailValue = m_detailResult.getInt( locateDetailCols.findColumn( detailColName ) ); 417 418 assertTrue( "values in linked column pair " + detailColName + "->" + masterColName + " (" + 419 detailValue + "->" + masterValue + ") do not match (master position: " + m_masterResult.getRow() + ")!", 420 masterValue == detailValue ); 421 } 422 disposing(com.sun.star.lang.EventObject eventObject)423 public void disposing(com.sun.star.lang.EventObject eventObject) 424 { 425 } 426 loaded(com.sun.star.lang.EventObject eventObject)427 public void loaded(com.sun.star.lang.EventObject eventObject) 428 { 429 synchronized( m_waitForLoad ) 430 { 431 m_loaded = true; 432 m_waitForLoad.notify(); 433 } 434 } 435 reloaded(com.sun.star.lang.EventObject eventObject)436 public void reloaded(com.sun.star.lang.EventObject eventObject) 437 { 438 synchronized( m_waitForLoad ) 439 { 440 m_loaded = true; 441 m_waitForLoad.notify(); 442 } 443 } 444 reloading(com.sun.star.lang.EventObject eventObject)445 public void reloading(com.sun.star.lang.EventObject eventObject) 446 { 447 } 448 unloaded(com.sun.star.lang.EventObject eventObject)449 public void unloaded(com.sun.star.lang.EventObject eventObject) 450 { 451 } 452 unloading(com.sun.star.lang.EventObject eventObject)453 public void unloading(com.sun.star.lang.EventObject eventObject) 454 { 455 } 456 } 457