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