xref: /trunk/main/odk/examples/DevelopersGuide/Forms/KeyGenerator.java (revision cdf0e10c4e3984b49a9502b011690b615761d4a3)
1 /*************************************************************************
2  *
3  *  The Contents of this file are made available subject to the terms of
4  *  the BSD license.
5  *
6  *  Copyright 2000, 2010 Oracle and/or its affiliates.
7  *  All rights reserved.
8  *
9  *  Redistribution and use in source and binary forms, with or without
10  *  modification, are permitted provided that the following conditions
11  *  are met:
12  *  1. Redistributions of source code must retain the above copyright
13  *     notice, this list of conditions and the following disclaimer.
14  *  2. Redistributions in binary form must reproduce the above copyright
15  *     notice, this list of conditions and the following disclaimer in the
16  *     documentation and/or other materials provided with the distribution.
17  *  3. Neither the name of Sun Microsystems, Inc. nor the names of its
18  *     contributors may be used to endorse or promote products derived
19  *     from this software without specific prior written permission.
20  *
21  *  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
22  *  "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
23  *  LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
24  *  FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
25  *  COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
26  *  INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
27  *  BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
28  *  OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
29  *  ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR
30  *  TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE
31  *  USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
32  *
33  *************************************************************************/
34 
35 import com.sun.star.uno.*;
36 import com.sun.star.beans.*;
37 import com.sun.star.form.*;
38 import com.sun.star.lang.*;
39 import com.sun.star.sdb.*;
40 import com.sun.star.sdbc.*;
41 import com.sun.star.sdbcx.*;
42 import com.sun.star.container.*;
43 import com.sun.star.awt.*;
44 
45 /**************************************************************************/
46 /** base class for helpers dealing with unique column values
47 */
48 class UniqueColumnValue
49 {
50     /* ------------------------------------------------------------------ */
51     /** extracts the name of the table a form is based on.
52 
53         <p>This method works for forms based directly on tables, and for forms based on statements, which
54         themself are based on one table.<br/>
55         Everything else (especially forms based on queries) is not yet implemented.</p>
56     */
57     protected String extractTableName( XPropertySet xForm ) throws com.sun.star.uno.Exception
58     {
59         String sReturn;
60 
61         Integer aCommandType = (Integer)xForm.getPropertyValue( "CommandType" );
62         String sCommand = (String)xForm.getPropertyValue( "Command" );
63 
64         if ( CommandType.COMMAND == aCommandType.intValue() )
65         {
66             // get the connection from the form
67             XConnection xFormConn = (XConnection)UnoRuntime.queryInterface( XConnection.class,
68                 xForm.getPropertyValue( "ActiveConnection" ) );
69             // and let it create a composer for us
70             XSQLQueryComposerFactory xComposerFac =
71                 (XSQLQueryComposerFactory)UnoRuntime.queryInterface(
72                     XSQLQueryComposerFactory.class, xFormConn );
73             XSQLQueryComposer xComposer = xComposerFac.createQueryComposer( );
74 
75             // let this composer analyze the command
76             xComposer.setQuery( sCommand );
77 
78             // and ask it for the table(s)
79             XTablesSupplier xSuppTables = (XTablesSupplier)UnoRuntime.queryInterface(
80                 XTablesSupplier.class, xComposer );
81             XNameAccess xTables = xSuppTables.getTables();
82 
83             // simply take the first table name
84             String[] aNames = xTables.getElementNames( );
85             sCommand = aNames[0];
86         }
87 
88         return sCommand;
89     }
90 
91     /* ------------------------------------------------------------------ */
92     /** generates a statement which can be used to create a unique (in all conscience) value
93         for the column given.
94         <p>Currently, the implementation uses a very simple approach - it just determines the maximum of currently
95         existing values in the column. If your concrete data source supports a more sophisticated approach of generating
96         unique values, you probably want to adjust the <code>SELECT</code> statement below accordingly.</p>
97 
98         @returns
99             a String which can be used as statement to retrieve a unique value for the given column.
100             The result set resulting from such a execution contains the value in it's first column.
101     */
102     protected String composeUniqueyKeyStatement( XPropertySet xForm, String sFieldName ) throws com.sun.star.uno.Exception
103     {
104         String sStatement = new String( "SELECT MAX( " );
105         sStatement += sFieldName;
106         sStatement += new String( ") + 1 FROM " );
107         // the table name is a property of the form
108         sStatement += extractTableName( xForm );
109 
110         // note that the implementation is imperfect (besides the problem that MAX is not a really good solution
111         // for a database with more that one client):
112         // It does not quote the field and the table name. This needs to be done if the database is intolerant
113         // against such things - the XDatabaseMetaData, obtained from the connection, would be needed then
114         // Unfortunately, there is no UNO service doing this - it would need to be implemented manually.
115 
116         return sStatement;
117     }
118 
119     /* ------------------------------------------------------------------ */
120     /** generates a unique (in all conscience) key into the column given
121         @param xForm
122             the form which contains the column in question
123         @param sFieldName
124             the name of the column
125     */
126     protected int generatePrimaryKey( XPropertySet xForm, String sFieldName ) throws com.sun.star.uno.Exception
127     {
128         // get the current connection of the form
129         XConnection xConn = (XConnection)UnoRuntime.queryInterface(
130             XConnection.class, xForm.getPropertyValue( "ActiveConnection" ) );
131         // let it create a new statement
132         XStatement xStatement = xConn.createStatement();
133 
134         // build the query string to determine a free value
135         String sStatement = composeUniqueyKeyStatement( xForm, sFieldName );
136 
137         // execute the query
138         XResultSet xResults = xStatement.executeQuery( sStatement );
139 
140         // move the result set to the first record
141         xResults.next( );
142 
143         // get the value
144         XRow xRow = (XRow)UnoRuntime.queryInterface( XRow.class, xResults );
145         int nFreeValue = xRow.getInt( 1 );
146 
147         // dispose the temporary objects
148         FLTools.disposeComponent( xStatement );
149             // this should get rid of the result set, too
150 
151         return nFreeValue;
152     }
153 
154     /* ------------------------------------------------------------------ */
155     /** inserts a unique (in all conscience) key into the column given
156         @param xForm
157             the form which contains the column in question
158         @param sFieldName
159             the name of the column
160     */
161     public void insertPrimaryKey( XPropertySet xForm, String sFieldName ) throws com.sun.star.uno.Exception
162     {
163         // check the privileges
164         Integer aConcurrency = (Integer)xForm.getPropertyValue( "ResultSetConcurrency" );
165         if ( ResultSetConcurrency.READ_ONLY != aConcurrency.intValue() )
166         {
167             // get the column object
168             XColumnsSupplier xSuppCols = (XColumnsSupplier)UnoRuntime.queryInterface(
169                 XColumnsSupplier.class, xForm );
170             XNameAccess xCols = xSuppCols.getColumns();
171             XColumnUpdate xCol = (XColumnUpdate)UnoRuntime.queryInterface(
172                 XColumnUpdate.class, xCols.getByName( sFieldName ) );
173 
174             xCol.updateInt( generatePrimaryKey( xForm, sFieldName ) );
175         }
176     }
177 };
178 
179 /**************************************************************************/
180 /** base class for helpers dealing with unique column values
181 */
182 class KeyGeneratorForReset extends UniqueColumnValue implements XResetListener
183 {
184     /* ------------------------------------------------------------------ */
185     private DocumentViewHelper  m_aView;
186     private String              m_sFieldName;
187 
188     /* ------------------------------------------------------------------ */
189     /** ctor
190         @param aView
191             the view which shall be used to focus controls
192         @param sFieldName
193             the name of the field for which keys should be generated
194     */
195     public KeyGeneratorForReset( String sFieldName, DocumentViewHelper aView )
196     {
197         m_sFieldName = sFieldName;
198         m_aView = aView;
199     }
200 
201     /* ------------------------------------------------------------------ */
202     /** sets the focus to the first control which is no fixed text, and not the
203         one we're defaulting
204     */
205     public void defaultNewRecordFocus( XPropertySet xForm ) throws com.sun.star.uno.Exception
206     {
207         XIndexAccess xFormAsContainer = (XIndexAccess)UnoRuntime.queryInterface(
208             XIndexAccess.class, xForm );
209         for ( int i = 0; i<xFormAsContainer.getCount(); ++i )
210         {
211             // the model
212             XPropertySet xModel = UNO.queryPropertySet( xFormAsContainer.getByIndex( i ) );
213 
214             // check if it's a valid leaf (no sub form or such)
215             XPropertySetInfo xPSI = xModel.getPropertySetInfo( );
216             if ( ( null == xPSI ) || !xPSI.hasPropertyByName( "ClassId" ) )
217                 continue;
218 
219             // check if it's a fixed text
220             Short nClassId = (Short)xModel.getPropertyValue( "ClassId" );
221             if ( FormComponentType.FIXEDTEXT == nClassId.shortValue() )
222                 continue;
223 
224             // check if it is bound to the field we are responsible for
225             if ( !xPSI.hasPropertyByName( "DataField" ) )
226                 continue;
227 
228             String sFieldDataSource = (String)xModel.getPropertyValue( "DataField" );
229             if ( sFieldDataSource.equals( m_sFieldName ) )
230                 continue;
231 
232             // both conditions do not apply
233             // -> set the focus into the respective control
234             XControlModel xCM = UNO.queryControlModel( xModel );
235             m_aView.grabControlFocus( xCM);
236             break;
237         }
238     }
239 
240     /* ------------------------------------------------------------------ */
241     // XResetListener overridables
242     /* ------------------------------------------------------------------ */
243     public boolean approveReset( com.sun.star.lang.EventObject rEvent ) throws com.sun.star.uno.RuntimeException
244     {
245         // not interested in vetoing this
246         return true;
247     }
248 
249     /* ------------------------------------------------------------------ */
250     public void resetted( com.sun.star.lang.EventObject aEvent ) throws com.sun.star.uno.RuntimeException
251     {
252         // check if this reset occured becase we're on a new record
253         XPropertySet xFormProps = UNO.queryPropertySet( aEvent.Source );
254         try
255         {
256             Boolean aIsNew = (Boolean)xFormProps.getPropertyValue( "IsNew" );
257             if ( aIsNew.booleanValue() )
258             {   // yepp
259 
260                 // we're going to modify the record, though after that, to the user, it should look
261                 // like it has not been modified
262                 // So we need to ensure that we do not change the IsModified property with whatever we do
263                 Object aModifiedFlag = xFormProps.getPropertyValue( "IsModified" );
264 
265                 // now set the value
266                 insertPrimaryKey( xFormProps, m_sFieldName );
267 
268                 // then restore the flag
269                 xFormProps.setPropertyValue( "IsModified", aModifiedFlag );
270 
271                 // still one thing ... would be nice to have the focus in a control which is
272                 // the one which's value we just defaulted
273                 defaultNewRecordFocus( xFormProps );
274             }
275         }
276         catch( com.sun.star.uno.Exception e )
277         {
278             System.out.println(e);
279             e.printStackTrace();
280         }
281     }
282     /* ------------------------------------------------------------------ */
283     // XEventListener overridables
284     /* ------------------------------------------------------------------ */
285     public void disposing( EventObject aEvent )
286     {
287         // not interested in
288     }
289 };
290 
291 
292 /**************************************************************************/
293 /** base class for helpers dealing with unique column values
294 */
295 class KeyGeneratorForUpdate extends UniqueColumnValue implements XRowSetApproveListener
296 {
297     /* ------------------------------------------------------------------ */
298     private String  m_sFieldName;
299 
300     /* ------------------------------------------------------------------ */
301     public KeyGeneratorForUpdate( String sFieldName )
302     {
303         m_sFieldName = sFieldName;
304     }
305 
306     /* ------------------------------------------------------------------ */
307     // XRowSetApproveListener overridables
308     /* ------------------------------------------------------------------ */
309     public boolean approveCursorMove( com.sun.star.lang.EventObject aEvent ) throws com.sun.star.uno.RuntimeException
310     {
311         // not interested in vetoing moves
312         return true;
313     }
314 
315     /* ------------------------------------------------------------------ */
316     public boolean approveRowChange( RowChangeEvent aEvent ) throws com.sun.star.uno.RuntimeException
317     {
318         if ( RowChangeAction.INSERT == aEvent.Action )
319         {
320             try
321             {
322                 // the affected form
323                 XPropertySet xFormProps = UNO.queryPropertySet( aEvent.Source );
324                 // insert a new unique value
325                 insertPrimaryKey( xFormProps, m_sFieldName );
326             }
327             catch( com.sun.star.uno.Exception e )
328             {
329                 System.out.println(e);
330                 e.printStackTrace();
331             }
332         }
333         return true;
334     }
335 
336     /* ------------------------------------------------------------------ */
337     public boolean approveRowSetChange( com.sun.star.lang.EventObject aEvent ) throws com.sun.star.uno.RuntimeException
338     {
339         // not interested in vetoing executions of the row set
340         return true;
341     }
342     /* ------------------------------------------------------------------ */
343     // XEventListener overridables
344     /* ------------------------------------------------------------------ */
345     public void disposing( EventObject aEvent )
346     {
347         // not interested in
348     }
349 };
350 
351 /**************************************************************************/
352 /** allows to generate unique keys for a field of a Form
353 */
354 public class KeyGenerator
355 {
356     /* ------------------------------------------------------------------ */
357     private KeyGeneratorForReset    m_aResetKeyGenerator;
358     private KeyGeneratorForUpdate   m_aUpdateKeyGenerator;
359     private boolean                 m_bResetListening;
360     private boolean                 m_bUpdateListening;
361 
362     private DocumentHelper          m_aDocument;
363     private XPropertySet            m_xForm;
364 
365     /* ------------------------------------------------------------------ */
366     /** ctor
367         @param xForm
368             specified the form to operate on
369         @param sFieldName
370             specifies the field which's value should be manipulated
371     */
372     public KeyGenerator( XPropertySet xForm, String sFieldName,
373                          XComponentContext xCtx )
374     {
375         m_xForm = xForm;
376 
377         DocumentHelper aDocument = DocumentHelper.getDocumentForComponent( xForm, xCtx );
378 
379         m_aResetKeyGenerator = new KeyGeneratorForReset( sFieldName, aDocument.getCurrentView() );
380         m_aUpdateKeyGenerator = new KeyGeneratorForUpdate( sFieldName );
381 
382         m_bResetListening = m_bUpdateListening = false;
383     }
384 
385     /* ------------------------------------------------------------------ */
386     /** stops any actions on the form
387     */
388     public void stopGenerator( )
389     {
390         XReset xFormReset = UNO.queryReset( m_xForm );
391         xFormReset.removeResetListener( m_aResetKeyGenerator );
392 
393         XRowSetApproveBroadcaster xFormBroadcaster = (XRowSetApproveBroadcaster)UnoRuntime.queryInterface(
394             XRowSetApproveBroadcaster.class, m_xForm );
395         xFormBroadcaster.removeRowSetApproveListener( m_aUpdateKeyGenerator );
396 
397         m_bUpdateListening = m_bResetListening = false;
398     }
399 
400     /* ------------------------------------------------------------------ */
401     /** activates one of our two key generators
402     */
403     public void activateKeyGenerator( boolean bGenerateOnReset )
404     {
405         // for resets
406         XReset xFormReset = UNO.queryReset( m_xForm );
407         // for approving actions
408         XRowSetApproveBroadcaster xFormBroadcaster = (XRowSetApproveBroadcaster)UnoRuntime.queryInterface(
409             XRowSetApproveBroadcaster.class, m_xForm );
410 
411         if ( bGenerateOnReset )
412         {
413             if ( !m_bResetListening )
414                 xFormReset.addResetListener( m_aResetKeyGenerator );
415             if ( m_bUpdateListening )
416                 xFormBroadcaster.removeRowSetApproveListener( m_aUpdateKeyGenerator );
417 
418             m_bUpdateListening = false;
419             m_bResetListening = true;
420         }
421         else
422         {
423             if ( m_bResetListening )
424                 xFormReset.removeResetListener( m_aResetKeyGenerator );
425             if ( !m_bUpdateListening )
426                 xFormBroadcaster.addRowSetApproveListener( m_aUpdateKeyGenerator );
427 
428             m_bResetListening = false;
429             m_bUpdateListening = true;
430         }
431     }
432 };
433