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