1 /*************************************************************************
2  *
3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4  *
5  * Copyright 2000, 2010 Oracle and/or its affiliates.
6  *
7  * OpenOffice.org - a multi-platform office productivity suite
8  *
9  * This file is part of OpenOffice.org.
10  *
11  * OpenOffice.org is free software: you can redistribute it and/or modify
12  * it under the terms of the GNU Lesser General Public License version 3
13  * only, as published by the Free Software Foundation.
14  *
15  * OpenOffice.org is distributed in the hope that it will be useful,
16  * but WITHOUT ANY WARRANTY; without even the implied warranty of
17  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
18  * GNU Lesser General Public License version 3 for more details
19  * (a copy is included in the LICENSE file that accompanied this code).
20  *
21  * You should have received a copy of the GNU Lesser General Public License
22  * version 3 along with OpenOffice.org.  If not, see
23  * <http://www.openoffice.org/license.html>
24  * for a copy of the LGPLv3 License.
25  *
26  ************************************************************************/
27 
28 // MARKER(update_precomp.py): autogen include statement, do not remove
29 #include "precompiled_ucb.hxx"
30 
31 #include <cachedcontentresultsetstub.hxx>
32 #include <com/sun/star/sdbc/FetchDirection.hpp>
33 #include <com/sun/star/ucb/FetchError.hpp>
34 #include <osl/diagnose.h>
35 
36 using namespace com::sun::star::beans;
37 using namespace com::sun::star::lang;
38 using namespace com::sun::star::sdbc;
39 using namespace com::sun::star::ucb;
40 using namespace com::sun::star::uno;
41 using namespace com::sun::star::util;
42 using namespace cppu;
43 using namespace rtl;
44 
45 CachedContentResultSetStub::CachedContentResultSetStub( Reference< XResultSet > xOrigin	)
46 				: ContentResultSetWrapper( xOrigin )
47 				, m_nColumnCount( 0 )
48 				, m_bColumnCountCached( sal_False )
49 				, m_bNeedToPropagateFetchSize( sal_True )
50 				, m_bFirstFetchSizePropagationDone( sal_False )
51 				, m_nLastFetchSize( 1 )//this value is not important at all
52 				, m_bLastFetchDirection( sal_True )//this value is not important at all
53 				, m_aPropertyNameForFetchSize( OUString::createFromAscii( "FetchSize" ) )
54 				, m_aPropertyNameForFetchDirection( OUString::createFromAscii( "FetchDirection" ) )
55 {
56 	impl_init();
57 }
58 
59 CachedContentResultSetStub::~CachedContentResultSetStub()
60 {
61 	impl_deinit();
62 }
63 
64 //--------------------------------------------------------------------------
65 // XInterface methods.
66 //--------------------------------------------------------------------------
67 XINTERFACE_COMMON_IMPL( CachedContentResultSetStub )
68 
69 Any SAL_CALL CachedContentResultSetStub
70 	::queryInterface( const Type&  rType )
71 	throw ( RuntimeException )
72 {
73 	//list all interfaces inclusive baseclasses of interfaces
74 
75 	Any aRet = ContentResultSetWrapper::queryInterface( rType );
76 	if( aRet.hasValue() )
77 		return aRet;
78 
79 	aRet = cppu::queryInterface( rType
80 				, static_cast< XTypeProvider* >( this )
81 				, static_cast< XServiceInfo* >( this )
82 				, static_cast< XFetchProvider* >( this )
83 				, static_cast< XFetchProviderForContentAccess* >( this )
84 				);
85 
86 	return aRet.hasValue() ? aRet : OWeakObject::queryInterface( rType );
87 }
88 
89 //--------------------------------------------------------------------------
90 // own methods.  ( inherited )
91 //--------------------------------------------------------------------------
92 
93 //virtual
94 void SAL_CALL CachedContentResultSetStub
95 	::impl_propertyChange( const PropertyChangeEvent& rEvt )
96 	throw( RuntimeException )
97 {
98 	impl_EnsureNotDisposed();
99 
100 	//don't notify events on fetchsize and fetchdirection to the above CachedContentResultSet
101 	//because it will ignore them anyway and we can save this remote calls
102 	if(	   rEvt.PropertyName == m_aPropertyNameForFetchSize
103 		|| rEvt.PropertyName == m_aPropertyNameForFetchDirection )
104 		return;
105 
106 	PropertyChangeEvent aEvt( rEvt );
107 	aEvt.Source = static_cast< XPropertySet * >( this );
108 	aEvt.Further = sal_False;
109 
110 	impl_notifyPropertyChangeListeners(	aEvt );
111 }
112 
113 
114 //virtual
115 void SAL_CALL CachedContentResultSetStub
116 	::impl_vetoableChange( const PropertyChangeEvent& rEvt )
117 	throw( PropertyVetoException,
118 		   RuntimeException )
119 {
120 	impl_EnsureNotDisposed();
121 
122 	//don't notify events on fetchsize and fetchdirection to the above CachedContentResultSet
123 	//because it will ignore them anyway and we can save this remote calls
124 	if(	   rEvt.PropertyName == m_aPropertyNameForFetchSize
125 		|| rEvt.PropertyName == m_aPropertyNameForFetchDirection )
126 		return;
127 
128 	PropertyChangeEvent aEvt( rEvt );
129 	aEvt.Source = static_cast< XPropertySet * >( this );
130 	aEvt.Further = sal_False;
131 
132 	impl_notifyVetoableChangeListeners( aEvt );
133 }
134 
135 //--------------------------------------------------------------------------
136 // XTypeProvider methods.
137 //--------------------------------------------------------------------------
138 
139 XTYPEPROVIDER_COMMON_IMPL( CachedContentResultSetStub )
140 //list all interfaces exclusive baseclasses
141 Sequence< Type > SAL_CALL CachedContentResultSetStub
142 	::getTypes()
143 	throw( RuntimeException )
144 {
145 	static Sequence< Type >* pTypes = NULL;
146 	if( !pTypes )
147 	{
148 		osl::Guard< osl::Mutex > aGuard( osl::Mutex::getGlobalMutex() );
149 		if( !pTypes )
150 		{
151 			pTypes = new Sequence< Type >(13);
152 			(*pTypes)[0] = CPPU_TYPE_REF( XTypeProvider );
153 			(*pTypes)[1] = CPPU_TYPE_REF( XServiceInfo );
154 			(*pTypes)[2] = CPPU_TYPE_REF( XComponent );
155 			(*pTypes)[3] = CPPU_TYPE_REF( XCloseable );
156 			(*pTypes)[4] = CPPU_TYPE_REF( XResultSetMetaDataSupplier );
157 			(*pTypes)[5] = CPPU_TYPE_REF( XPropertySet );
158 			(*pTypes)[6] = CPPU_TYPE_REF( XPropertyChangeListener );
159 			(*pTypes)[7] = CPPU_TYPE_REF( XVetoableChangeListener );
160 			(*pTypes)[8] = CPPU_TYPE_REF( XResultSet );
161 			(*pTypes)[9] = CPPU_TYPE_REF( XContentAccess );
162 			(*pTypes)[10] = CPPU_TYPE_REF( XRow );
163 			(*pTypes)[11] = CPPU_TYPE_REF( XFetchProvider );
164 			(*pTypes)[12] = CPPU_TYPE_REF( XFetchProviderForContentAccess );
165 		}
166 	}
167 	return *pTypes;
168 	/*
169 	static cppu::OTypeCollection * pCollection = 0;
170 	if (!pCollection)
171 	{
172 		osl::MutexGuard aGuard(osl::Mutex::getGlobalMutex());
173 		if (!pCollection)
174 		{
175 			static cppu::OTypeCollection
176 				aTheCollection(
177 					getCppuType(
178 						static_cast< Reference< XTypeProvider >
179 						                 const * >(
180 							0)),
181 					getCppuType(
182 						static_cast< Reference< XServiceInfo >
183 						                 const * >(
184 							0)),
185 					getCppuType(
186 						static_cast< Reference< XComponent >
187 						                 const * >(
188 							0)),
189 					getCppuType(
190 						static_cast< Reference< XCloseable >
191 						                 const * >(
192 							0)),
193 					getCppuType(
194 						static_cast< Reference< XResultSetMetaDataSupplier >
195 						                 const * >(
196 							0)),
197 					getCppuType(
198 						static_cast< Reference< XPropertySet >
199 						                 const * >(
200 							0)),
201 					getCppuType(
202 						static_cast< Reference< XPropertyChangeListener >
203 						                 const * >(
204 							0)),
205 					getCppuType(
206 						static_cast< Reference< XVetoableChangeListener >
207 						                 const * >(
208 							0)),
209 					getCppuType(
210 						static_cast< Reference< XResultSet >
211 						                 const * >(
212 							0)),
213 					getCppuType(
214 						static_cast< Reference< XContentAccess >
215 						                 const * >(
216 							0)),
217 					getCppuType(
218 						static_cast< Reference< XRow >
219 						                 const * >(
220 							0)),
221 					getCppuType(
222 						static_cast< Reference< XFetchProvider >
223 						                 const * >(
224 							0)),
225 					getCppuType(
226 						static_cast< Reference< XFetchProviderForContentAccess >
227 						                 const * >(
228 							0))
229 							);
230 			pCollection = &aTheCollection;
231 		}
232 	}
233 	return pCollection->getTypes();
234 	*/
235 }
236 
237 //--------------------------------------------------------------------------
238 // XServiceInfo methods.
239 //--------------------------------------------------------------------------
240 
241 XSERVICEINFO_NOFACTORY_IMPL_1( CachedContentResultSetStub,
242 			 		   OUString::createFromAscii(
243 						"com.sun.star.comp.ucb.CachedContentResultSetStub" ),
244 			 		   OUString::createFromAscii(
245 						CACHED_CRS_STUB_SERVICE_NAME ) );
246 
247 //-----------------------------------------------------------------
248 // XFetchProvider methods.
249 //-----------------------------------------------------------------
250 
251 #define FETCH_XXX( impl_loadRow, loadInterface ) \
252 impl_EnsureNotDisposed(); \
253 if( !m_xResultSetOrigin.is() ) \
254 { \
255 	OSL_ENSURE( sal_False, "broadcaster was disposed already" ); \
256 	throw RuntimeException(); \
257 } \
258 impl_propagateFetchSizeAndDirection( nRowCount, bDirection ); \
259 FetchResult aRet; \
260 aRet.StartIndex = nRowStartPosition; \
261 aRet.Orientation = bDirection; \
262 aRet.FetchError = FetchError::SUCCESS; /*ENDOFDATA, EXCEPTION*/ \
263 sal_Int32 nOldOriginal_Pos = m_xResultSetOrigin->getRow(); \
264 if( impl_isForwardOnly() ) \
265 { \
266 	if( nOldOriginal_Pos != nRowStartPosition ) \
267 	{ \
268 		/*@todo*/ \
269 		aRet.FetchError = FetchError::EXCEPTION; \
270 		return aRet; \
271 	} \
272 	if( nRowCount != 1 ) \
273 		aRet.FetchError = FetchError::EXCEPTION; \
274  \
275 	aRet.Rows.realloc( 1 ); \
276  \
277 	try \
278 	{ \
279 		impl_loadRow( aRet.Rows[0], loadInterface ); \
280 	} \
281 	catch( SQLException& ) \
282 	{ \
283 		aRet.Rows.realloc( 0 ); \
284 		aRet.FetchError = FetchError::EXCEPTION; \
285 		return aRet; \
286 	} \
287 	return aRet; \
288 } \
289 aRet.Rows.realloc( nRowCount ); \
290 sal_Bool bOldOriginal_AfterLast = sal_False; \
291 if( !nOldOriginal_Pos ) \
292 	bOldOriginal_AfterLast = m_xResultSetOrigin->isAfterLast(); \
293 sal_Int32 nN = 1; \
294 sal_Bool bValidNewPos = sal_False; \
295 try \
296 { \
297 	try \
298 	{ \
299 		/*if( nOldOriginal_Pos != nRowStartPosition )*/ \
300 		bValidNewPos = m_xResultSetOrigin->absolute( nRowStartPosition ); \
301 	} \
302 	catch( SQLException& ) \
303 	{ \
304 		aRet.Rows.realloc( 0 ); \
305 		aRet.FetchError = FetchError::EXCEPTION; \
306 		return aRet; \
307 	} \
308 	if( !bValidNewPos ) \
309 	{ \
310 		aRet.Rows.realloc( 0 ); \
311 		aRet.FetchError = FetchError::EXCEPTION; \
312  \
313 		/*restore old position*/ \
314 		if( nOldOriginal_Pos ) \
315 			m_xResultSetOrigin->absolute( nOldOriginal_Pos ); \
316 		else if( bOldOriginal_AfterLast ) \
317 			m_xResultSetOrigin->afterLast(); \
318 		else \
319 			m_xResultSetOrigin->beforeFirst(); \
320  \
321 		return aRet; \
322 	} \
323 	for( ; nN <= nRowCount; ) \
324 	{ \
325 		impl_loadRow( aRet.Rows[nN-1], loadInterface ); \
326 		nN++; \
327 		if( nN <= nRowCount ) \
328 		{ \
329 			if( bDirection ) \
330 			{ \
331 				if( !m_xResultSetOrigin->next() ) \
332 				{ \
333 					aRet.Rows.realloc( nN-1 ); \
334 					aRet.FetchError = FetchError::ENDOFDATA; \
335 					break; \
336 				} \
337 			} \
338 			else \
339 			{ \
340 				if( !m_xResultSetOrigin->previous() ) \
341 				{ \
342 					aRet.Rows.realloc( nN-1 ); \
343 					aRet.FetchError = FetchError::ENDOFDATA; \
344 					break; \
345 				} \
346 			} \
347 		} \
348 	} \
349 } \
350 catch( SQLException& ) \
351 { \
352 	aRet.Rows.realloc( nN-1 ); \
353 	aRet.FetchError = FetchError::EXCEPTION; \
354 } \
355 /*restore old position*/ \
356 if( nOldOriginal_Pos ) \
357 	m_xResultSetOrigin->absolute( nOldOriginal_Pos ); \
358 else if( bOldOriginal_AfterLast ) \
359 	m_xResultSetOrigin->afterLast(); \
360 else \
361 	m_xResultSetOrigin->beforeFirst(); \
362 return aRet;
363 
364 FetchResult SAL_CALL CachedContentResultSetStub
365 	::fetch( sal_Int32 nRowStartPosition
366 	, sal_Int32 nRowCount, sal_Bool bDirection )
367 	throw( RuntimeException )
368 {
369 	impl_init_xRowOrigin();
370 	FETCH_XXX( impl_getCurrentRowContent, m_xRowOrigin );
371 }
372 
373 sal_Int32 SAL_CALL CachedContentResultSetStub
374 	::impl_getColumnCount()
375 {
376 	sal_Int32 nCount;
377 	sal_Bool bCached;
378 	{
379 		osl::Guard< osl::Mutex > aGuard( m_aMutex );
380 		nCount = m_nColumnCount;
381 		bCached = m_bColumnCountCached;
382 	}
383 	if( !bCached )
384 	{
385 		try
386 		{
387 			Reference< XResultSetMetaData > xMetaData = getMetaData();
388 			if( xMetaData.is() )
389 				nCount = xMetaData->getColumnCount();
390 		}
391 		catch( SQLException& )
392 		{
393 			OSL_ENSURE( sal_False, "couldn't determine the column count" );
394 			nCount = 0;
395 		}
396 	}
397 	osl::Guard< osl::Mutex > aGuard( m_aMutex );
398 	m_nColumnCount = nCount;
399 	m_bColumnCountCached = sal_True;
400 	return m_nColumnCount;
401 }
402 
403 void SAL_CALL CachedContentResultSetStub
404 	::impl_getCurrentRowContent( Any& rRowContent
405 		, Reference< XRow > xRow )
406 		throw ( SQLException, RuntimeException )
407 {
408 	sal_Int32 nCount = impl_getColumnCount();
409 
410 	Sequence< Any > aContent( nCount );
411 	for( sal_Int32 nN = 1; nN <= nCount; nN++ )
412 	{
413 		aContent[nN-1] = xRow->getObject( nN, NULL );
414 	}
415 
416 	rRowContent <<= aContent;
417 }
418 
419 void SAL_CALL CachedContentResultSetStub
420 	::impl_propagateFetchSizeAndDirection( sal_Int32 nFetchSize, sal_Bool bFetchDirection )
421 		throw ( RuntimeException )
422 {
423 	//this is done only for the case, that there is another CachedContentResultSet in the chain of underlying ResulSets
424 
425 	//we do not propagate the property 'FetchSize' or 'FetchDirection' via 'setPropertyValue' from the above CachedContentResultSet to save remote calls
426 
427 	//if the underlying ResultSet has a property FetchSize and FetchDirection,
428 	//we will set these properties, if the new given parameters are different from the last ones
429 
430 	if( !m_bNeedToPropagateFetchSize )
431 		return;
432 
433 	sal_Bool bNeedAction;
434 	sal_Int32 nLastSize;
435 	sal_Bool bLastDirection;
436 	sal_Bool bFirstPropagationDone;
437 	{
438 		osl::Guard< osl::Mutex > aGuard( m_aMutex );
439 		bNeedAction				= m_bNeedToPropagateFetchSize;
440 		nLastSize				= m_nLastFetchSize;
441 		bLastDirection			= m_bLastFetchDirection;
442 		bFirstPropagationDone	= m_bFirstFetchSizePropagationDone;
443 	}
444 	if( bNeedAction )
445 	{
446 		if( nLastSize == nFetchSize
447 			&& bLastDirection == bFetchDirection
448 			&& bFirstPropagationDone == sal_True )
449 			return;
450 
451 		if(!bFirstPropagationDone)
452 		{
453 			//check wether the properties 'FetchSize' and 'FetchDirection' do exist
454 
455 			Reference< XPropertySetInfo > xPropertySetInfo = getPropertySetInfo();
456 			sal_Bool bHasSize = xPropertySetInfo->hasPropertyByName( m_aPropertyNameForFetchSize );
457 			sal_Bool bHasDirection = xPropertySetInfo->hasPropertyByName( m_aPropertyNameForFetchDirection );
458 
459 			if(!bHasSize || !bHasDirection)
460 			{
461 				osl::Guard< osl::Mutex > aGuard( m_aMutex );
462 				m_bNeedToPropagateFetchSize = sal_False;
463 				return;
464 			}
465 		}
466 
467 		sal_Bool bSetSize		= ( nLastSize		!=nFetchSize		) || !bFirstPropagationDone;
468 		sal_Bool bSetDirection	= ( bLastDirection	!=bFetchDirection	) || !bFirstPropagationDone;
469 
470 		{
471 			osl::Guard< osl::Mutex > aGuard( m_aMutex );
472 			m_bFirstFetchSizePropagationDone = sal_True;
473 			m_nLastFetchSize		= nFetchSize;
474 			m_bLastFetchDirection	= bFetchDirection;
475 		}
476 
477 		if( bSetSize )
478 		{
479 			Any aValue;
480 			aValue <<= nFetchSize;
481 			try
482 			{
483 				setPropertyValue( m_aPropertyNameForFetchSize, aValue );
484 			}
485 			catch( com::sun::star::uno::Exception& ) {}
486 		}
487 		if( bSetDirection )
488 		{
489 			sal_Int32 nFetchDirection = FetchDirection::FORWARD;
490 			if( !bFetchDirection )
491 				nFetchDirection = FetchDirection::REVERSE;
492 			Any aValue;
493 			aValue <<= nFetchDirection;
494 			try
495 			{
496 				setPropertyValue( m_aPropertyNameForFetchDirection, aValue );
497 			}
498 			catch( com::sun::star::uno::Exception& ) {}
499 		}
500 
501 	}
502 }
503 
504 //-----------------------------------------------------------------
505 // XFetchProviderForContentAccess methods.
506 //-----------------------------------------------------------------
507 
508 void SAL_CALL CachedContentResultSetStub
509 	::impl_getCurrentContentIdentifierString( Any& rAny
510 		, Reference< XContentAccess > xContentAccess )
511 		throw ( RuntimeException )
512 {
513  	rAny <<= xContentAccess->queryContentIdentifierString();
514 }
515 
516 void SAL_CALL CachedContentResultSetStub
517 	::impl_getCurrentContentIdentifier( Any& rAny
518 		, Reference< XContentAccess > xContentAccess )
519 		throw ( RuntimeException )
520 {
521  	rAny <<= xContentAccess->queryContentIdentifier();
522 }
523 
524 void SAL_CALL CachedContentResultSetStub
525 	::impl_getCurrentContent( Any& rAny
526 		, Reference< XContentAccess > xContentAccess )
527 		throw ( RuntimeException )
528 {
529  	rAny <<= xContentAccess->queryContent();
530 }
531 
532 //virtual
533 FetchResult SAL_CALL CachedContentResultSetStub
534 	::fetchContentIdentifierStrings( sal_Int32 nRowStartPosition
535 		, sal_Int32 nRowCount, sal_Bool bDirection )
536 		throw( com::sun::star::uno::RuntimeException )
537 {
538 	impl_init_xContentAccessOrigin();
539 	FETCH_XXX( impl_getCurrentContentIdentifierString, m_xContentAccessOrigin );
540 }
541 
542 //virtual
543 FetchResult SAL_CALL CachedContentResultSetStub
544 	::fetchContentIdentifiers( sal_Int32 nRowStartPosition
545 		, sal_Int32 nRowCount, sal_Bool bDirection )
546 		throw( com::sun::star::uno::RuntimeException )
547 {
548 	impl_init_xContentAccessOrigin();
549 	FETCH_XXX( impl_getCurrentContentIdentifier, m_xContentAccessOrigin );
550 }
551 
552 //virtual
553 FetchResult SAL_CALL CachedContentResultSetStub
554 	::fetchContents( sal_Int32 nRowStartPosition
555 		, sal_Int32 nRowCount, sal_Bool bDirection )
556 		throw( com::sun::star::uno::RuntimeException )
557 {
558 	impl_init_xContentAccessOrigin();
559 	FETCH_XXX( impl_getCurrentContent, m_xContentAccessOrigin );
560 }
561 
562 //--------------------------------------------------------------------------
563 //--------------------------------------------------------------------------
564 // class CachedContentResultSetStubFactory
565 //--------------------------------------------------------------------------
566 //--------------------------------------------------------------------------
567 
568 CachedContentResultSetStubFactory::CachedContentResultSetStubFactory(
569 		const Reference< XMultiServiceFactory > & rSMgr )
570 {
571 	m_xSMgr = rSMgr;
572 }
573 
574 CachedContentResultSetStubFactory::~CachedContentResultSetStubFactory()
575 {
576 }
577 
578 //--------------------------------------------------------------------------
579 // CachedContentResultSetStubFactory XInterface methods.
580 //--------------------------------------------------------------------------
581 
582 XINTERFACE_IMPL_3( CachedContentResultSetStubFactory,
583 				   XTypeProvider,
584 				   XServiceInfo,
585 				   XCachedContentResultSetStubFactory );
586 
587 //--------------------------------------------------------------------------
588 // CachedContentResultSetStubFactory XTypeProvider methods.
589 //--------------------------------------------------------------------------
590 
591 XTYPEPROVIDER_IMPL_3( CachedContentResultSetStubFactory,
592 					  XTypeProvider,
593 				   	  XServiceInfo,
594 					  XCachedContentResultSetStubFactory );
595 
596 //--------------------------------------------------------------------------
597 // CachedContentResultSetStubFactory XServiceInfo methods.
598 //--------------------------------------------------------------------------
599 
600 XSERVICEINFO_IMPL_1( CachedContentResultSetStubFactory,
601  		   		 OUString::createFromAscii(
602 				 	"com.sun.star.comp.ucb.CachedContentResultSetStubFactory" ),
603  		   		 OUString::createFromAscii(
604 				 	CACHED_CRS_STUB_FACTORY_NAME ) );
605 
606 //--------------------------------------------------------------------------
607 // Service factory implementation.
608 //--------------------------------------------------------------------------
609 
610 ONE_INSTANCE_SERVICE_FACTORY_IMPL( CachedContentResultSetStubFactory );
611 
612 //--------------------------------------------------------------------------
613 // CachedContentResultSetStubFactory XCachedContentResultSetStubFactory methods.
614 //--------------------------------------------------------------------------
615 
616 	//virtual
617 Reference< XResultSet > SAL_CALL CachedContentResultSetStubFactory
618 	::createCachedContentResultSetStub(
619 			const Reference< XResultSet > & xSource )
620 			throw( RuntimeException )
621 {
622 	if( xSource.is() )
623 	{
624 		Reference< XResultSet > xRet;
625 		xRet = new CachedContentResultSetStub( xSource );
626 		return xRet;
627 	}
628 	return NULL;
629 }
630 
631 
632