xref: /trunk/main/forms/qa/integration/forms/MasterDetailForms.java (revision 3309286857f19787ae62bd793a98b5af4edd2ad3)
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