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