1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2 /*
3 * This file is part of the LibreOffice project.
4 *
5 * This Source Code Form is subject to the terms of the Mozilla Public
6 * License, v. 2.0. If a copy of the MPL was not distributed with this
7 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
8 *
9 * This file incorporates work covered by the following license notice:
10 *
11 * Licensed to the Apache Software Foundation (ASF) under one or more
12 * contributor license agreements. See the NOTICE file distributed
13 * with this work for additional information regarding copyright
14 * ownership. The ASF licenses this file to you under the Apache
15 * License, Version 2.0 (the "License"); you may not use this file
16 * except in compliance with the License. You may obtain a copy of
17 * the License at http://www.apache.org/licenses/LICENSE-2.0 .
18 */
19
20 #include <memory>
21 #include <strings.hrc>
22 #include <strings.hxx>
23 #include <core_resource.hxx>
24 #include <WCopyTable.hxx>
25
26 #include <com/sun/star/lang/XMultiServiceFactory.hpp>
27 #include <com/sun/star/sdb/application/XCopyTableWizard.hpp>
28 #include <com/sun/star/sdb/application/CopyTableContinuation.hpp>
29 #include <com/sun/star/sdb/application/CopyTableOperation.hpp>
30 #include <com/sun/star/ucb/AlreadyInitializedException.hpp>
31 #include <com/sun/star/lang/NotInitializedException.hpp>
32 #include <com/sun/star/sdbc/XDataSource.hpp>
33 #include <com/sun/star/sdbc/DataType.hpp>
34 #include <com/sun/star/container/XNameAccess.hpp>
35 #include <com/sun/star/container/XChild.hpp>
36 #include <com/sun/star/task/InteractionHandler.hpp>
37 #include <com/sun/star/frame/XModel.hpp>
38 #include <com/sun/star/sdb/DatabaseContext.hpp>
39 #include <com/sun/star/sdb/XDocumentDataSource.hpp>
40 #include <com/sun/star/sdb/XCompletedConnection.hpp>
41 #include <com/sun/star/sdb/CommandType.hpp>
42 #include <com/sun/star/sdbcx/XTablesSupplier.hpp>
43 #include <com/sun/star/sdb/XQueriesSupplier.hpp>
44 #include <com/sun/star/lang/DisposedException.hpp>
45 #include <com/sun/star/sdb/XSingleSelectQueryComposer.hpp>
46 #include <com/sun/star/sdbc/XParameters.hpp>
47 #include <com/sun/star/sdbc/XRow.hpp>
48 #include <com/sun/star/sdbcx/XRowLocate.hpp>
49 #include <com/sun/star/sdbc/XResultSetMetaDataSupplier.hpp>
50 #include <com/sun/star/sdb/SQLContext.hpp>
51 #include <com/sun/star/sdbc/DriverManager.hpp>
52 #include <com/sun/star/sdbc/ConnectionPool.hpp>
53
54 #include <comphelper/processfactory.hxx>
55 #include <comphelper/interaction.hxx>
56 #include <comphelper/namedvaluecollection.hxx>
57 #include <comphelper/proparrhlp.hxx>
58 #include <connectivity/dbexception.hxx>
59 #include <connectivity/dbtools.hxx>
60 #include <cppuhelper/exc_hlp.hxx>
61 #include <cppuhelper/implbase.hxx>
62 #include <comphelper/interfacecontainer3.hxx>
63 #include <o3tl/safeint.hxx>
64 #include <rtl/ustrbuf.hxx>
65 #include <sal/log.hxx>
66 #include <svtools/genericunodialog.hxx>
67 #include <toolkit/helper/vclunohelper.hxx>
68 #include <comphelper/diagnose_ex.hxx>
69 #include <unotools/sharedunocomponent.hxx>
70 #include <vcl/svapp.hxx>
71
72 namespace dbaui
73 {
74
75 using ::com::sun::star::uno::Reference;
76 using ::com::sun::star::uno::XInterface;
77 using ::com::sun::star::uno::UNO_QUERY;
78 using ::com::sun::star::uno::UNO_QUERY_THROW;
79 using ::com::sun::star::uno::UNO_SET_THROW;
80 using ::com::sun::star::uno::Exception;
81 using ::com::sun::star::uno::RuntimeException;
82 using ::com::sun::star::uno::Any;
83 using ::com::sun::star::uno::Sequence;
84 using ::com::sun::star::uno::XComponentContext;
85 using ::com::sun::star::beans::XPropertySetInfo;
86 using ::com::sun::star::lang::XMultiServiceFactory;
87 using ::com::sun::star::beans::Property;
88 using ::com::sun::star::sdb::application::XCopyTableWizard;
89 using ::com::sun::star::sdb::application::XCopyTableListener;
90 using ::com::sun::star::sdb::application::CopyTableRowEvent;
91 using ::com::sun::star::beans::Optional;
92 using ::com::sun::star::lang::IllegalArgumentException;
93 using ::com::sun::star::ucb::AlreadyInitializedException;
94 using ::com::sun::star::beans::XPropertySet;
95 using ::com::sun::star::lang::NotInitializedException;
96 using ::com::sun::star::lang::XServiceInfo;
97 using ::com::sun::star::sdbc::XConnection;
98 using ::com::sun::star::sdbc::XDataSource;
99 using ::com::sun::star::container::XNameAccess;
100 using ::com::sun::star::container::XChild;
101 using ::com::sun::star::task::InteractionHandler;
102 using ::com::sun::star::task::XInteractionHandler;
103 using ::com::sun::star::frame::XModel;
104 using ::com::sun::star::sdb::DatabaseContext;
105 using ::com::sun::star::sdb::XDatabaseContext;
106 using ::com::sun::star::sdb::XDocumentDataSource;
107 using ::com::sun::star::sdb::XCompletedConnection;
108 using ::com::sun::star::lang::WrappedTargetException;
109 using ::com::sun::star::sdbcx::XTablesSupplier;
110 using ::com::sun::star::sdb::XQueriesSupplier;
111 using ::com::sun::star::lang::DisposedException;
112 using ::com::sun::star::sdbc::XPreparedStatement;
113 using ::com::sun::star::sdb::XSingleSelectQueryComposer;
114 using ::com::sun::star::sdbc::XDatabaseMetaData;
115 using ::com::sun::star::sdbcx::XColumnsSupplier;
116 using ::com::sun::star::sdbc::XParameters;
117 using ::com::sun::star::sdbc::XResultSet;
118 using ::com::sun::star::sdbc::XRow;
119 using ::com::sun::star::sdbcx::XRowLocate;
120 using ::com::sun::star::sdbc::XResultSetMetaDataSupplier;
121 using ::com::sun::star::sdbc::XResultSetMetaData;
122 using ::com::sun::star::sdbc::SQLException;
123 using ::com::sun::star::sdb::SQLContext;
124 using ::com::sun::star::sdbc::ConnectionPool;
125 using ::com::sun::star::sdbc::XDriverManager;
126 using ::com::sun::star::sdbc::DriverManager;
127 using ::com::sun::star::beans::PropertyValue;
128
129 namespace CopyTableOperation = ::com::sun::star::sdb::application::CopyTableOperation;
130 namespace CopyTableContinuation = ::com::sun::star::sdb::application::CopyTableContinuation;
131 namespace CommandType = ::com::sun::star::sdb::CommandType;
132 namespace DataType = ::com::sun::star::sdbc::DataType;
133
134 typedef ::utl::SharedUNOComponent< XConnection > SharedConnection;
135
136 // CopyTableWizard
137 typedef ::svt::OGenericUnoDialog CopyTableWizard_DialogBase;
138 typedef ::cppu::ImplInheritanceHelper< CopyTableWizard_DialogBase
139 , XCopyTableWizard
140 > CopyTableWizard_Base;
141
142 namespace {
143
144 class CopyTableWizard
145 :public CopyTableWizard_Base
146 ,public ::comphelper::OPropertyArrayUsageHelper< CopyTableWizard >
147 {
148 public:
149 // XServiceInfo
150 virtual OUString SAL_CALL getImplementationName() override;
151 virtual css::uno::Sequence<OUString> SAL_CALL getSupportedServiceNames() override;
152
153 // XCopyTableWizard
154 virtual ::sal_Int16 SAL_CALL getOperation() override;
155 virtual void SAL_CALL setOperation( ::sal_Int16 _operation ) override;
156 virtual OUString SAL_CALL getDestinationTableName() override;
157 virtual void SAL_CALL setDestinationTableName( const OUString& _destinationTableName ) override;
158 virtual Optional< OUString > SAL_CALL getCreatePrimaryKey() override;
159 virtual void SAL_CALL setCreatePrimaryKey( const Optional< OUString >& _newPrimaryKey ) override;
160 virtual sal_Bool SAL_CALL getUseHeaderLineAsColumnNames() override;
161 virtual void SAL_CALL setUseHeaderLineAsColumnNames( sal_Bool _bUseHeaderLineAsColumnNames ) override;
162 virtual void SAL_CALL addCopyTableListener( const Reference< XCopyTableListener >& Listener ) override;
163 virtual void SAL_CALL removeCopyTableListener( const Reference< XCopyTableListener >& Listener ) override;
164
165 // XCopyTableWizard::XExecutableDialog
166 virtual void SAL_CALL setTitle( const OUString& aTitle ) override;
167 virtual ::sal_Int16 SAL_CALL execute( ) override;
168
169 // XInitialization
170 virtual void SAL_CALL initialize( const Sequence< Any >& aArguments ) override;
171
172 // XPropertySet
173 virtual Reference< XPropertySetInfo > SAL_CALL getPropertySetInfo() override;
174 virtual ::cppu::IPropertyArrayHelper& SAL_CALL getInfoHelper() override;
175
176 // OPropertyArrayUsageHelper
177 virtual ::cppu::IPropertyArrayHelper* createArrayHelper( ) const override;
178
179 public:
getMutex()180 ::osl::Mutex& getMutex() { return m_aMutex; }
isInitialized() const181 bool isInitialized() const { return m_xSourceConnection.is() && m_pSourceObject && m_xDestConnection.is(); }
182
183 explicit CopyTableWizard( const Reference< XComponentContext >& _rxORB );
184 virtual ~CopyTableWizard() override;
185
186 protected:
187 // OGenericUnoDialog overridables
188 virtual std::unique_ptr<weld::DialogController> createDialog(const css::uno::Reference<css::awt::XWindow>& rParent) override;
189 virtual void executedDialog( sal_Int16 _nExecutionResult ) override;
190
191 private:
192 /// ensures our current attribute values are reflected in the dialog
193 void impl_attributesToDialog_nothrow( OCopyTableWizard& _rDialog ) const;
194
195 /// ensures the current dialog settings are reflected in our attributes
196 void impl_dialogToAttributes_nothrow( const OCopyTableWizard& _rDialog );
197
198 /** returns our typed dialog
199
200 @throws css::uno::RuntimeException
201 if we don't have a dialog at the moment the method is called
202 */
203 OCopyTableWizard&
204 impl_getDialog_throw();
205
206 /** ensures the given argument sequence contains a valid data access descriptor at the given position
207 @param _rAllArgs
208 the arguments as passed to ->initialize
209 @param _nArgPos
210 the position within ->_rAllArgs which contains the data access descriptor
211 @param _out_rxConnection
212 will, upon successful return, contain the connection for the data source
213 @param _out_rxDocInteractionHandler
214 will, upon successful return, contain the interaction handler which could
215 be deduced from database document described by the descriptor, if any.
216 (It is possible that the descriptor does not allow to deduce a database document,
217 in which case <code>_out_rxDocInteractionHandler</code> will be <NULL/>.)
218 @return the data access descriptor
219 */
220 Reference< XPropertySet >
221 impl_ensureDataAccessDescriptor_throw(
222 const Sequence< Any >& _rAllArgs,
223 const sal_Int16 _nArgPos,
224 SharedConnection& _out_rxConnection,
225 Reference< XInteractionHandler >& _out_rxDocInteractionHandler
226 ) const;
227
228 /** extracts the source object (table or query) described by the given descriptor,
229 relative to m_xSourceConnection
230 */
231 std::unique_ptr< ICopyTableSourceObject >
232 impl_extractSourceObject_throw(
233 const Reference< XPropertySet >& _rxDescriptor,
234 sal_Int32& _out_rCommandType
235 ) const;
236
237 /** extracts the result set to copy records from, and the selection-related aspects, if any.
238
239 Effectively, this method extracts m_xSourceResultSet, m_aSourceSelection, and m_bSourceSelectionBookmarks.
240
241 If an inconsistent/insufficient sub set of those properties is present in the descriptor, and exception
242 is thrown.
243 */
244 void impl_extractSourceResultSet_throw(
245 const Reference< XPropertySet >& i_rDescriptor
246 );
247
248 /** checks whether the given copy source descriptor contains settings which are not
249 supported (yet)
250
251 Throws an IllegalArgumentException if the descriptor contains a valid setting, which is
252 not yet supported.
253 */
254 void impl_checkForUnsupportedSettings_throw(
255 const Reference< XPropertySet >& _rxSourceDescriptor ) const;
256
257 /** obtains the connection described by the given data access descriptor
258
259 If needed and possible, the method will ask the user, using the interaction
260 handler associated with the database described by the descriptor.
261
262 All errors are handled with the InteractionHandler associated with the data source,
263 if there is one. Else, they will be silenced (but asserted in non-product builds).
264
265 @param _rxDataSourceDescriptor
266 the data access descriptor describing the data source whose connection
267 should be obtained. Must not be <NULL/>.
268 @param _out_rxDocInteractionHandler
269 the interaction handler which could be deduced from the descriptor
270
271 @throws RuntimeException
272 if anything goes seriously wrong.
273 */
274 SharedConnection
275 impl_extractConnection_throw(
276 const Reference< XPropertySet >& _rxDataSourceDescriptor,
277 Reference< XInteractionHandler >& _out_rxDocInteractionHandler
278 ) const;
279
280 /** actually copies the table
281
282 This method is called after the dialog has been successfully executed.
283 */
284 void impl_doCopy_nothrow();
285
286 /** creates the INSERT INTO statement
287 @param _xTable The destination table.
288 */
289 OUString impl_getServerSideCopyStatement_throw( const Reference< XPropertySet >& _xTable );
290
291 /** creates the statement which, when executed, will produce the source data to copy
292
293 If the source object refers to a query which contains parameters, those parameters
294 are filled in, using an interaction handler.
295 */
296 ::utl::SharedUNOComponent< XPreparedStatement >
297 impl_createSourceStatement_throw() const;
298
299 /** copies the data rows from the given source result set to the given destination table
300 */
301 void impl_copyRows_throw(
302 const Reference< XResultSet >& _rxSourceResultSet,
303 const Reference< XPropertySet >& _rxDestTable
304 );
305
306 /** processes an error which occurred during copying
307
308 First, all listeners are ask. If a listener tells to cancel or continue copying, this is reported to the
309 method's caller. If a listener tells to ask the user, this is done, and the user's decision is
310 reported to the method's caller.
311
312 @return
313 <TRUE/> if and only if copying should be continued.
314 */
315 bool impl_processCopyError_nothrow(
316 const CopyTableRowEvent& _rEvent );
317
318 private:
319 Reference<XComponentContext> m_xContext;
320
321 // attributes
322 sal_Int16 m_nOperation;
323 OUString m_sDestinationTable;
324 Optional< OUString > m_aPrimaryKeyName;
325 bool m_bUseHeaderLineAsColumnNames;
326
327 // source
328 SharedConnection m_xSourceConnection;
329 sal_Int32 m_nCommandType;
330 std::unique_ptr< ICopyTableSourceObject >
331 m_pSourceObject;
332 Reference< XResultSet > m_xSourceResultSet;
333 Sequence< Any > m_aSourceSelection;
334 bool m_bSourceSelectionBookmarks;
335
336 // destination
337 SharedConnection m_xDestConnection;
338
339 // other
340 Reference< XInteractionHandler > m_xInteractionHandler;
341 ::comphelper::OInterfaceContainerHelper3<XCopyTableListener>
342 m_aCopyTableListeners;
343 sal_Int16 m_nOverrideExecutionResult;
344 };
345
346 // MethodGuard
347 class CopyTableAccessGuard
348 {
349 public:
CopyTableAccessGuard(CopyTableWizard & _rWizard)350 explicit CopyTableAccessGuard( CopyTableWizard& _rWizard )
351 :m_rWizard( _rWizard )
352 {
353 m_rWizard.getMutex().acquire();
354 if ( !m_rWizard.isInitialized() )
355 throw NotInitializedException();
356 }
357
~CopyTableAccessGuard()358 ~CopyTableAccessGuard()
359 {
360 m_rWizard.getMutex().release();
361 }
362
363 private:
364 CopyTableWizard& m_rWizard;
365 };
366
367 }
368
CopyTableWizard(const Reference<XComponentContext> & _rxORB)369 CopyTableWizard::CopyTableWizard( const Reference< XComponentContext >& _rxORB )
370 :CopyTableWizard_Base( _rxORB )
371 ,m_xContext( _rxORB )
372 ,m_nOperation( CopyTableOperation::CopyDefinitionAndData )
373 ,m_aPrimaryKeyName( false, u"ID"_ustr )
374 ,m_bUseHeaderLineAsColumnNames( true )
375 ,m_nCommandType( CommandType::COMMAND )
376 ,m_bSourceSelectionBookmarks( true )
377 ,m_aCopyTableListeners( m_aMutex )
378 ,m_nOverrideExecutionResult( -1 )
379 {
380 }
381
~CopyTableWizard()382 CopyTableWizard::~CopyTableWizard()
383 {
384 acquire();
385
386 // protect some members whose dtor might potentially throw
387 try { m_xSourceConnection.clear(); }
388 catch( const Exception& ) { DBG_UNHANDLED_EXCEPTION("dbaccess"); }
389 try { m_xDestConnection.clear(); }
390 catch( const Exception& ) { DBG_UNHANDLED_EXCEPTION("dbaccess"); }
391
392 // TODO: shouldn't we have explicit disposal support? If a listener is registered
393 // at our instance, and perhaps holds this our instance by a hard ref, then we'll never
394 // be destroyed.
395 // However, adding XComponent support to the GenericUNODialog probably requires
396 // some thinking - would it break existing clients which do not call a dispose, then?
397 }
398
getImplementationName()399 OUString SAL_CALL CopyTableWizard::getImplementationName()
400 {
401 return u"org.openoffice.comp.dbu.CopyTableWizard"_ustr;
402 }
403
getSupportedServiceNames()404 css::uno::Sequence<OUString> SAL_CALL CopyTableWizard::getSupportedServiceNames()
405 {
406 return { u"com.sun.star.sdb.application.CopyTableWizard"_ustr };
407 }
408
getPropertySetInfo()409 Reference< XPropertySetInfo > SAL_CALL CopyTableWizard::getPropertySetInfo()
410 {
411 Reference< XPropertySetInfo > xInfo( createPropertySetInfo( getInfoHelper() ) );
412 return xInfo;
413 }
414
getOperation()415 ::sal_Int16 SAL_CALL CopyTableWizard::getOperation()
416 {
417 CopyTableAccessGuard aGuard( *this );
418 return m_nOperation;
419 }
420
setOperation(::sal_Int16 _operation)421 void SAL_CALL CopyTableWizard::setOperation( ::sal_Int16 _operation )
422 {
423 CopyTableAccessGuard aGuard( *this );
424
425 if ( ( _operation != CopyTableOperation::CopyDefinitionAndData )
426 && ( _operation != CopyTableOperation::CopyDefinitionOnly )
427 && ( _operation != CopyTableOperation::CreateAsView )
428 && ( _operation != CopyTableOperation::AppendData )
429 )
430 throw IllegalArgumentException( OUString(), *this, 1 );
431
432 if ( ( _operation == CopyTableOperation::CreateAsView )
433 && !OCopyTableWizard::supportsViews( m_xDestConnection )
434 )
435 throw IllegalArgumentException(
436 DBA_RES( STR_CTW_NO_VIEWS_SUPPORT ),
437 *this,
438 1
439 );
440
441 m_nOperation = _operation;
442 }
443
getDestinationTableName()444 OUString SAL_CALL CopyTableWizard::getDestinationTableName()
445 {
446 CopyTableAccessGuard aGuard( *this );
447 return m_sDestinationTable;
448 }
449
setDestinationTableName(const OUString & _destinationTableName)450 void SAL_CALL CopyTableWizard::setDestinationTableName( const OUString& _destinationTableName )
451 {
452 CopyTableAccessGuard aGuard( *this );
453 m_sDestinationTable = _destinationTableName;
454 }
455
getCreatePrimaryKey()456 Optional< OUString > SAL_CALL CopyTableWizard::getCreatePrimaryKey()
457 {
458 CopyTableAccessGuard aGuard( *this );
459 return m_aPrimaryKeyName;
460 }
461
setCreatePrimaryKey(const Optional<OUString> & _newPrimaryKey)462 void SAL_CALL CopyTableWizard::setCreatePrimaryKey( const Optional< OUString >& _newPrimaryKey )
463 {
464 CopyTableAccessGuard aGuard( *this );
465
466 if ( _newPrimaryKey.IsPresent && !OCopyTableWizard::supportsPrimaryKey( m_xDestConnection ) )
467 throw IllegalArgumentException(
468 DBA_RES( STR_CTW_NO_PRIMARY_KEY_SUPPORT ),
469 *this,
470 1
471 );
472
473 m_aPrimaryKeyName = _newPrimaryKey;
474 }
475
getUseHeaderLineAsColumnNames()476 sal_Bool SAL_CALL CopyTableWizard::getUseHeaderLineAsColumnNames()
477 {
478 CopyTableAccessGuard aGuard( *this );
479 return m_bUseHeaderLineAsColumnNames;
480 }
481
setUseHeaderLineAsColumnNames(sal_Bool _bUseHeaderLineAsColumnNames)482 void SAL_CALL CopyTableWizard::setUseHeaderLineAsColumnNames( sal_Bool _bUseHeaderLineAsColumnNames )
483 {
484 CopyTableAccessGuard aGuard( *this );
485 m_bUseHeaderLineAsColumnNames = _bUseHeaderLineAsColumnNames;
486 }
487
addCopyTableListener(const Reference<XCopyTableListener> & _rxListener)488 void SAL_CALL CopyTableWizard::addCopyTableListener( const Reference< XCopyTableListener >& _rxListener )
489 {
490 CopyTableAccessGuard aGuard( *this );
491 if ( _rxListener.is() )
492 m_aCopyTableListeners.addInterface( _rxListener );
493 }
494
removeCopyTableListener(const Reference<XCopyTableListener> & _rxListener)495 void SAL_CALL CopyTableWizard::removeCopyTableListener( const Reference< XCopyTableListener >& _rxListener )
496 {
497 CopyTableAccessGuard aGuard( *this );
498 if ( _rxListener.is() )
499 m_aCopyTableListeners.removeInterface( _rxListener );
500 }
501
setTitle(const OUString & _rTitle)502 void SAL_CALL CopyTableWizard::setTitle( const OUString& _rTitle )
503 {
504 CopyTableAccessGuard aGuard( *this );
505 CopyTableWizard_DialogBase::setTitle( _rTitle );
506 }
507
execute()508 ::sal_Int16 SAL_CALL CopyTableWizard::execute( )
509 {
510 CopyTableAccessGuard aGuard( *this );
511
512 m_nOverrideExecutionResult = -1;
513 sal_Int16 nExecutionResult = CopyTableWizard_DialogBase::execute();
514 if ( m_nOverrideExecutionResult )
515 nExecutionResult = m_nOverrideExecutionResult;
516
517 return nExecutionResult;
518 }
519
impl_getDialog_throw()520 OCopyTableWizard& CopyTableWizard::impl_getDialog_throw()
521 {
522 OCopyTableWizard* pWizard = dynamic_cast<OCopyTableWizard*>(m_xDialog.get());
523 if ( !pWizard )
524 throw DisposedException( OUString(), *this );
525 return *pWizard;
526 }
527
impl_attributesToDialog_nothrow(OCopyTableWizard & _rDialog) const528 void CopyTableWizard::impl_attributesToDialog_nothrow( OCopyTableWizard& _rDialog ) const
529 {
530 // primary key column
531 _rDialog.setCreatePrimaryKey( m_aPrimaryKeyName.IsPresent, m_aPrimaryKeyName.Value );
532 _rDialog.setUseHeaderLine(m_bUseHeaderLineAsColumnNames);
533
534 // everything else was passed at construction time already
535 }
536
impl_dialogToAttributes_nothrow(const OCopyTableWizard & _rDialog)537 void CopyTableWizard::impl_dialogToAttributes_nothrow( const OCopyTableWizard& _rDialog )
538 {
539 m_aPrimaryKeyName.IsPresent = _rDialog.shouldCreatePrimaryKey();
540 if ( m_aPrimaryKeyName.IsPresent )
541 m_aPrimaryKeyName.Value = _rDialog.getPrimaryKeyName();
542 else
543 m_aPrimaryKeyName.Value.clear();
544
545 m_sDestinationTable = _rDialog.getName();
546
547 m_nOperation = _rDialog.getOperation();
548 m_bUseHeaderLineAsColumnNames = _rDialog.UseHeaderLine();
549 }
550
551 namespace
552 {
553 /** tries to obtain the InteractionHandler associated with a given data source
554
555 If the data source is a sdb-level data source, it will have a DatabaseDocument associated
556 with it. This document may have an InteractionHandler used while loading it.
557
558 @throws RuntimeException
559 if it occurs during invoking any of the data source's methods, or if any of the involved
560 components violates its contract by not providing the required interfaces
561 */
lcl_getInteractionHandler_throw(const Reference<XDataSource> & _rxDataSource,const Reference<XInteractionHandler> & _rFallback)562 Reference< XInteractionHandler > lcl_getInteractionHandler_throw( const Reference< XDataSource >& _rxDataSource, const Reference< XInteractionHandler >& _rFallback )
563 {
564 Reference< XInteractionHandler > xHandler( _rFallback );
565
566 // try to obtain the document model
567 Reference< XModel > xDocumentModel;
568 Reference< XDocumentDataSource > xDocDataSource( _rxDataSource, UNO_QUERY );
569 if ( xDocDataSource.is() )
570 xDocumentModel.set( xDocDataSource->getDatabaseDocument(), UNO_QUERY_THROW );
571
572 // see whether the document model can provide a handler
573 if ( xDocumentModel.is() )
574 {
575 xHandler = ::comphelper::NamedValueCollection::getOrDefault( xDocumentModel->getArgs(), u"InteractionHandler", xHandler );
576 }
577
578 return xHandler;
579 }
580 /** tries to obtain the InteractionHandler associated with a given connection
581
582 If the connection belongs to a sdb-level data source, then this data source
583 is examined for an interaction handler. Else, <NULL/> is returned.
584
585 @throws RuntimeException
586 if it occurs during invoking any of the data source's methods, or if any of the involved
587 components violates its contract by not providing the required interfaces
588 */
lcl_getInteractionHandler_throw(const Reference<XConnection> & _rxConnection,const Reference<XInteractionHandler> & _rFallback)589 Reference< XInteractionHandler > lcl_getInteractionHandler_throw( const Reference< XConnection >& _rxConnection, const Reference< XInteractionHandler >& _rFallback )
590 {
591 // try whether there is a data source which the connection belongs to
592 Reference< XDataSource > xDataSource;
593 Reference< XChild > xAsChild( _rxConnection, UNO_QUERY );
594 if ( xAsChild.is() )
595 xDataSource.set(xAsChild->getParent(), css::uno::UNO_QUERY);
596
597 if ( xDataSource.is() )
598 return lcl_getInteractionHandler_throw( xDataSource, _rFallback );
599
600 return _rFallback;
601 }
602 }
603
impl_ensureDataAccessDescriptor_throw(const Sequence<Any> & _rAllArgs,const sal_Int16 _nArgPos,SharedConnection & _out_rxConnection,Reference<XInteractionHandler> & _out_rxDocInteractionHandler) const604 Reference< XPropertySet > CopyTableWizard::impl_ensureDataAccessDescriptor_throw(
605 const Sequence< Any >& _rAllArgs, const sal_Int16 _nArgPos, SharedConnection& _out_rxConnection,
606 Reference< XInteractionHandler >& _out_rxDocInteractionHandler ) const
607 {
608 Reference< XPropertySet > xDescriptor;
609 _rAllArgs[ _nArgPos ] >>= xDescriptor;
610
611 // the descriptor must be non-NULL, of course
612 bool bIsValid = xDescriptor.is();
613
614 // it must support the proper service
615 if ( bIsValid )
616 {
617 Reference< XServiceInfo > xSI( xDescriptor, UNO_QUERY );
618 bIsValid = ( xSI.is()
619 && xSI->supportsService( u"com.sun.star.sdb.DataAccessDescriptor"_ustr ) );
620 }
621
622 // it must be able to provide a connection
623 if ( bIsValid )
624 {
625 _out_rxConnection = impl_extractConnection_throw( xDescriptor, _out_rxDocInteractionHandler );
626 bIsValid = _out_rxConnection.is();
627 }
628
629 if ( !bIsValid )
630 {
631 throw IllegalArgumentException(
632 DBA_RES( STR_CTW_INVALID_DATA_ACCESS_DESCRIPTOR ),
633 *const_cast< CopyTableWizard* >( this ),
634 _nArgPos + 1
635 );
636 }
637
638 return xDescriptor;
639 }
640
641 namespace
642 {
lcl_hasNonEmptyStringValue_throw(const Reference<XPropertySet> & _rxDescriptor,const Reference<XPropertySetInfo> & rxPSI,const OUString & _rPropertyName)643 bool lcl_hasNonEmptyStringValue_throw( const Reference< XPropertySet >& _rxDescriptor,
644 const Reference< XPropertySetInfo >& rxPSI, const OUString& _rPropertyName )
645 {
646 OUString sValue;
647 if ( rxPSI->hasPropertyByName( _rPropertyName ) )
648 {
649 OSL_VERIFY( _rxDescriptor->getPropertyValue( _rPropertyName ) >>= sValue );
650 }
651 return !sValue.isEmpty();
652 }
653 }
654
impl_checkForUnsupportedSettings_throw(const Reference<XPropertySet> & _rxSourceDescriptor) const655 void CopyTableWizard::impl_checkForUnsupportedSettings_throw( const Reference< XPropertySet >& _rxSourceDescriptor ) const
656 {
657 OSL_PRECOND( _rxSourceDescriptor.is(), "CopyTableWizard::impl_checkForUnsupportedSettings_throw: illegal argument!" );
658 Reference< XPropertySetInfo > xPSI( _rxSourceDescriptor->getPropertySetInfo(), UNO_SET_THROW );
659 OUString sUnsupportedSetting;
660
661 const OUString aSettings[] = {
662 PROPERTY_FILTER, PROPERTY_ORDER, PROPERTY_HAVING_CLAUSE, PROPERTY_GROUP_BY
663 };
664 for (const auto & aSetting : aSettings)
665 {
666 if ( lcl_hasNonEmptyStringValue_throw( _rxSourceDescriptor, xPSI, aSetting ) )
667 {
668 sUnsupportedSetting = aSetting;
669 break;
670 }
671 }
672
673 if ( !sUnsupportedSetting.isEmpty() )
674 {
675 OUString sMessage(
676 DBA_RES(STR_CTW_ERROR_UNSUPPORTED_SETTING).
677 replaceFirst("$name$", sUnsupportedSetting));
678 throw IllegalArgumentException(
679 sMessage,
680 *const_cast< CopyTableWizard* >( this ),
681 1
682 );
683 }
684
685 }
686
impl_extractSourceObject_throw(const Reference<XPropertySet> & _rxDescriptor,sal_Int32 & _out_rCommandType) const687 std::unique_ptr< ICopyTableSourceObject > CopyTableWizard::impl_extractSourceObject_throw( const Reference< XPropertySet >& _rxDescriptor, sal_Int32& _out_rCommandType ) const
688 {
689 OSL_PRECOND( _rxDescriptor.is() && m_xSourceConnection.is(), "CopyTableWizard::impl_extractSourceObject_throw: illegal arguments!" );
690
691 Reference< XPropertySetInfo > xPSI( _rxDescriptor->getPropertySetInfo(), UNO_SET_THROW );
692 if ( !xPSI->hasPropertyByName( PROPERTY_COMMAND )
693 || !xPSI->hasPropertyByName( PROPERTY_COMMAND_TYPE )
694 )
695 throw IllegalArgumentException(u"Expecting a table or query specification."_ustr,
696 // TODO: resource
697 *const_cast< CopyTableWizard* >( this ), 1);
698
699 OUString sCommand;
700 _out_rCommandType = CommandType::COMMAND;
701 OSL_VERIFY( _rxDescriptor->getPropertyValue( PROPERTY_COMMAND ) >>= sCommand );
702 OSL_VERIFY( _rxDescriptor->getPropertyValue( PROPERTY_COMMAND_TYPE ) >>= _out_rCommandType );
703
704 std::unique_ptr< ICopyTableSourceObject > pSourceObject;
705 Reference< XNameAccess > xContainer;
706 switch ( _out_rCommandType )
707 {
708 case CommandType::TABLE:
709 {
710 Reference< XTablesSupplier > xSuppTables( m_xSourceConnection.getTyped(), UNO_QUERY );
711 if ( xSuppTables.is() )
712 xContainer.set( xSuppTables->getTables(), UNO_SET_THROW );
713 }
714 break;
715 case CommandType::QUERY:
716 {
717 Reference< XQueriesSupplier > xSuppQueries( m_xSourceConnection.getTyped(), UNO_QUERY );
718 if ( xSuppQueries.is() )
719 xContainer.set( xSuppQueries->getQueries(), UNO_SET_THROW );
720 }
721 break;
722 default:
723 throw IllegalArgumentException(
724 DBA_RES( STR_CTW_ONLY_TABLES_AND_QUERIES_SUPPORT ),
725 *const_cast< CopyTableWizard* >( this ),
726 1
727 );
728 }
729
730 if ( xContainer.is() )
731 {
732 pSourceObject.reset( new ObjectCopySource( m_xSourceConnection,
733 Reference< XPropertySet >( xContainer->getByName( sCommand ), UNO_QUERY_THROW ) ) );
734 }
735 else
736 {
737 // our source connection is an SDBC level connection only, not a SDBCX level one
738 // Which means it cannot provide the to-be-copied object as component.
739
740 if ( _out_rCommandType == CommandType::QUERY )
741 // we cannot copy a query if the connection cannot provide it ...
742 throw IllegalArgumentException(
743 DBA_RES( STR_CTW_ERROR_NO_QUERY ),
744 *const_cast< CopyTableWizard* >( this ),
745 1
746 );
747 pSourceObject.reset( new NamedTableCopySource( m_xSourceConnection, sCommand ) );
748 }
749
750 return pSourceObject;
751 }
752
impl_extractSourceResultSet_throw(const Reference<XPropertySet> & i_rDescriptor)753 void CopyTableWizard::impl_extractSourceResultSet_throw( const Reference< XPropertySet >& i_rDescriptor )
754 {
755 Reference< XPropertySetInfo > xPSI( i_rDescriptor->getPropertySetInfo(), UNO_SET_THROW );
756
757 // extract relevant settings
758 if ( xPSI->hasPropertyByName( PROPERTY_RESULT_SET ) )
759 m_xSourceResultSet.set( i_rDescriptor->getPropertyValue( PROPERTY_RESULT_SET ), UNO_QUERY );
760
761 if ( xPSI->hasPropertyByName( PROPERTY_SELECTION ) )
762 OSL_VERIFY( i_rDescriptor->getPropertyValue( PROPERTY_SELECTION ) >>= m_aSourceSelection );
763
764 if ( xPSI->hasPropertyByName( PROPERTY_BOOKMARK_SELECTION ) )
765 OSL_VERIFY( i_rDescriptor->getPropertyValue( PROPERTY_BOOKMARK_SELECTION ) >>= m_bSourceSelectionBookmarks );
766
767 // sanity checks
768 const bool bHasResultSet = m_xSourceResultSet.is();
769 const bool bHasSelection = m_aSourceSelection.hasElements();
770 if ( bHasSelection && !bHasResultSet )
771 throw IllegalArgumentException(u"A result set is needed when specifying a selection to copy."_ustr,
772 // TODO: resource
773 *this, 1);
774
775 if ( bHasSelection && m_bSourceSelectionBookmarks )
776 {
777 Reference< XRowLocate > xRowLocate( m_xSourceResultSet, UNO_QUERY );
778 if ( !xRowLocate.is() )
779 {
780 ::dbtools::throwGenericSQLException(
781 DBA_RES(STR_CTW_COPY_SOURCE_NEEDS_BOOKMARKS),
782 *this
783 );
784 }
785 }
786 }
787
impl_extractConnection_throw(const Reference<XPropertySet> & _rxDataSourceDescriptor,Reference<XInteractionHandler> & _out_rxDocInteractionHandler) const788 SharedConnection CopyTableWizard::impl_extractConnection_throw( const Reference< XPropertySet >& _rxDataSourceDescriptor,
789 Reference< XInteractionHandler >& _out_rxDocInteractionHandler ) const
790 {
791 SharedConnection xConnection;
792
793 OSL_PRECOND( _rxDataSourceDescriptor.is(), "CopyTableWizard::impl_extractConnection_throw: no descriptor!" );
794 if ( !_rxDataSourceDescriptor.is() )
795 return xConnection;
796
797 Reference< XInteractionHandler > xInteractionHandler;
798
799 do
800 {
801 Reference< XPropertySetInfo > xPSI( _rxDataSourceDescriptor->getPropertySetInfo(), UNO_SET_THROW );
802
803 // if there's an ActiveConnection, use it
804 if ( xPSI->hasPropertyByName( PROPERTY_ACTIVE_CONNECTION ) )
805 {
806 Reference< XConnection > xPure;
807 OSL_VERIFY( _rxDataSourceDescriptor->getPropertyValue( PROPERTY_ACTIVE_CONNECTION ) >>= xPure );
808 xConnection.reset( xPure, SharedConnection::NoTakeOwnership );
809 }
810 if ( xConnection.is() )
811 {
812 xInteractionHandler = lcl_getInteractionHandler_throw( xConnection.getTyped(), m_xInteractionHandler );
813 SAL_WARN_IF( !xInteractionHandler.is(), "dbaccess.ui", "CopyTableWizard::impl_extractConnection_throw: lcl_getInteractionHandler_throw returned nonsense!" );
814 break;
815 }
816
817 // there could be a DataSourceName or a DatabaseLocation, describing the css.sdb.DataSource
818 OUString sDataSource, sDatabaseLocation;
819 if ( xPSI->hasPropertyByName( PROPERTY_DATASOURCENAME ) )
820 OSL_VERIFY( _rxDataSourceDescriptor->getPropertyValue( PROPERTY_DATASOURCENAME ) >>= sDataSource );
821 if ( xPSI->hasPropertyByName( PROPERTY_DATABASE_LOCATION ) )
822 OSL_VERIFY( _rxDataSourceDescriptor->getPropertyValue( PROPERTY_DATABASE_LOCATION ) >>= sDatabaseLocation );
823
824 // need a DatabaseContext for loading the data source
825 Reference< XDatabaseContext > xDatabaseContext = DatabaseContext::create( m_xContext );
826 Reference< XDataSource > xDataSource;
827 if ( !sDataSource.isEmpty() )
828 xDataSource.set( xDatabaseContext->getByName( sDataSource ), UNO_QUERY_THROW );
829 if ( !xDataSource.is() && !sDatabaseLocation.isEmpty() )
830 xDataSource.set( xDatabaseContext->getByName( sDatabaseLocation ), UNO_QUERY_THROW );
831
832 if ( xDataSource.is() )
833 {
834 // first, try connecting with completion
835 xInteractionHandler = lcl_getInteractionHandler_throw( xDataSource, m_xInteractionHandler );
836 SAL_WARN_IF( !xInteractionHandler.is(), "dbaccess.ui", "CopyTableWizard::impl_extractConnection_throw: lcl_getInteractionHandler_throw returned nonsense!" );
837 if ( xInteractionHandler.is() )
838 {
839 Reference< XCompletedConnection > xInteractiveConnection( xDataSource, UNO_QUERY );
840 if ( xInteractiveConnection.is() )
841 xConnection.reset( xInteractiveConnection->connectWithCompletion( xInteractionHandler ), SharedConnection::TakeOwnership );
842 }
843
844 // interactively connecting was not successful or possible -> connect without interaction
845 if ( !xConnection.is() )
846 {
847 xConnection.reset( xDataSource->getConnection( OUString(), OUString() ), SharedConnection::TakeOwnership );
848 }
849 }
850
851 if ( xConnection.is() )
852 break;
853
854 // finally, there could be a ConnectionResource/ConnectionInfo
855 OUString sConnectionResource;
856 Sequence< PropertyValue > aConnectionInfo;
857 if ( xPSI->hasPropertyByName( PROPERTY_CONNECTION_RESOURCE ) )
858 OSL_VERIFY( _rxDataSourceDescriptor->getPropertyValue( PROPERTY_CONNECTION_RESOURCE ) >>= sConnectionResource );
859 if ( xPSI->hasPropertyByName( PROPERTY_CONNECTION_INFO ) )
860 OSL_VERIFY( _rxDataSourceDescriptor->getPropertyValue( PROPERTY_CONNECTION_INFO ) >>= aConnectionInfo );
861
862 Reference< XDriverManager > xDriverManager;
863 try {
864 xDriverManager.set( ConnectionPool::create( m_xContext ), UNO_QUERY_THROW );
865 } catch( const Exception& ) { }
866 if ( !xDriverManager.is() )
867 // no connection pool installed
868 xDriverManager.set( DriverManager::create( m_xContext ), UNO_QUERY_THROW );
869
870 if ( aConnectionInfo.hasElements() )
871 xConnection.set( xDriverManager->getConnectionWithInfo( sConnectionResource, aConnectionInfo ), UNO_SET_THROW );
872 else
873 xConnection.set( xDriverManager->getConnection( sConnectionResource ), UNO_SET_THROW );
874 }
875 while ( false );
876
877 if ( xInteractionHandler != m_xInteractionHandler )
878 _out_rxDocInteractionHandler = std::move(xInteractionHandler);
879
880 return xConnection;
881 }
882
impl_createSourceStatement_throw() const883 ::utl::SharedUNOComponent< XPreparedStatement > CopyTableWizard::impl_createSourceStatement_throw() const
884 {
885 OSL_PRECOND( m_xSourceConnection.is(), "CopyTableWizard::impl_createSourceStatement_throw: illegal call!" );
886 if ( !m_xSourceConnection.is() )
887 throw RuntimeException( u"CopyTableWizard::impl_createSourceStatement_throw: illegal call!"_ustr, *const_cast< CopyTableWizard* >( this ));
888
889 ::utl::SharedUNOComponent< XPreparedStatement > xStatement;
890 switch ( m_nCommandType )
891 {
892 case CommandType::TABLE:
893 xStatement.set( m_pSourceObject->getPreparedSelectStatement(), UNO_SET_THROW );
894 break;
895
896 case CommandType::QUERY:
897 {
898 OUString sQueryCommand( m_pSourceObject->getSelectStatement() );
899 xStatement.set( m_pSourceObject->getPreparedSelectStatement(), UNO_SET_THROW );
900
901 // check whether we have to fill in parameter values
902 // create and fill a composer
903
904 Reference< XMultiServiceFactory > xFactory( m_xSourceConnection, UNO_QUERY );
905 ::utl::SharedUNOComponent< XSingleSelectQueryComposer > xComposer;
906 if ( xFactory.is() )
907 // note: connections below the sdb-level are allowed to not support the XMultiServiceFactory interface
908 xComposer.set( xFactory->createInstance( SERVICE_NAME_SINGLESELECTQUERYCOMPOSER ), UNO_QUERY );
909
910 if ( xComposer.is() )
911 {
912 xComposer->setQuery( sQueryCommand );
913
914 Reference< XParameters > xStatementParams( xStatement, UNO_QUERY );
915 OSL_ENSURE( xStatementParams.is(), "CopyTableWizard::impl_createSourceStatement_throw: no access to the statement's parameters!" );
916 // the statement should be a css.sdbc.PreparedStatement (this is what
917 // we created), and a prepared statement is required to support XParameters
918 if ( xStatementParams.is() )
919 {
920 OSL_ENSURE( m_xInteractionHandler.is(),
921 "CopyTableWizard::impl_createSourceStatement_throw: no interaction handler for the parameters request!" );
922 // we should always have an interaction handler - as last fallback, we create an own one in ::initialize
923
924 if ( m_xInteractionHandler.is() )
925 ::dbtools::askForParameters( xComposer, xStatementParams, m_xSourceConnection, m_xInteractionHandler );
926 }
927 }
928 }
929 break;
930
931 default:
932 // this should not have survived initialization phase
933 throw RuntimeException(u"No case matched, this should not have survived the initialization phase"_ustr, *const_cast< CopyTableWizard* >( this ));
934 }
935
936 return xStatement;
937 }
938
939 namespace
940 {
941 class ValueTransfer
942 {
943 public:
ValueTransfer(std::vector<sal_Int32> _rColTypes,const Reference<XRow> & _rxSource,const Reference<XParameters> & _rxDest)944 ValueTransfer( std::vector< sal_Int32 > _rColTypes,
945 const Reference< XRow >& _rxSource, const Reference< XParameters >& _rxDest )
946 :m_ColTypes( std::move(_rColTypes) )
947 ,m_xSource( _rxSource )
948 ,m_xDest( _rxDest )
949 {
950 }
951
952 template< typename VALUE_TYPE >
transferValue(sal_Int32 _nSourcePos,sal_Int32 _nDestPos,VALUE_TYPE (SAL_CALL XRow::* _pGetter)(sal_Int32),void (SAL_CALL XParameters::* _pSetter)(sal_Int32,VALUE_TYPE))953 void transferValue( sal_Int32 _nSourcePos, sal_Int32 _nDestPos,
954 VALUE_TYPE ( SAL_CALL XRow::*_pGetter )( sal_Int32 ),
955 void (SAL_CALL XParameters::*_pSetter)( sal_Int32, VALUE_TYPE ) )
956 {
957 VALUE_TYPE value( (m_xSource.get()->*_pGetter)( _nSourcePos ) );
958 if ( m_xSource->wasNull() )
959 m_xDest->setNull( _nDestPos, m_ColTypes[ _nSourcePos ] );
960 else
961 (m_xDest.get()->*_pSetter)( _nDestPos, value );
962 }
963
964 template< typename VALUE_TYPE >
transferComplexValue(sal_Int32 _nSourcePos,sal_Int32 _nDestPos,VALUE_TYPE (SAL_CALL XRow::* _pGetter)(sal_Int32),void (SAL_CALL XParameters::* _pSetter)(sal_Int32,const VALUE_TYPE &))965 void transferComplexValue( sal_Int32 _nSourcePos, sal_Int32 _nDestPos,
966 VALUE_TYPE ( SAL_CALL XRow::*_pGetter )( sal_Int32 ),
967 void (SAL_CALL XParameters::*_pSetter)( sal_Int32, const VALUE_TYPE& ) )
968 {
969 const VALUE_TYPE value( (m_xSource.get()->*_pGetter)( _nSourcePos ) );
970 if ( m_xSource->wasNull() )
971 m_xDest->setNull( _nDestPos, m_ColTypes[ _nSourcePos ] );
972 else
973 (m_xDest.get()->*_pSetter)( _nDestPos, value );
974 }
975 private:
976 const std::vector< sal_Int32 > m_ColTypes;
977 const Reference< XRow > m_xSource;
978 const Reference< XParameters > m_xDest;
979 };
980 }
981
impl_processCopyError_nothrow(const CopyTableRowEvent & _rEvent)982 bool CopyTableWizard::impl_processCopyError_nothrow( const CopyTableRowEvent& _rEvent )
983 {
984 try
985 {
986 ::comphelper::OInterfaceIteratorHelper3 aIter( m_aCopyTableListeners );
987 while ( aIter.hasMoreElements() )
988 {
989 Reference< XCopyTableListener > xListener( aIter.next() );
990 sal_Int16 nListenerChoice = xListener->copyRowError( _rEvent );
991 switch ( nListenerChoice )
992 {
993 case CopyTableContinuation::Proceed: return true; // continue copying
994 case CopyTableContinuation::CallNextHandler: continue; // continue the loop, ask next listener
995 case CopyTableContinuation::Cancel: return false; // cancel copying
996 case CopyTableContinuation::AskUser: break; // stop asking the listeners, ask the user
997
998 default:
999 SAL_WARN("dbaccess.ui", "CopyTableWizard::impl_processCopyError_nothrow: invalid listener response!" );
1000 // ask next listener
1001 continue;
1002 }
1003 }
1004 }
1005 catch( const Exception& )
1006 {
1007 DBG_UNHANDLED_EXCEPTION("dbaccess");
1008 }
1009
1010 // no listener felt responsible for the error, or a listener told to ask the user
1011
1012 try
1013 {
1014 css::uno::Any next;
1015 ::dbtools::SQLExceptionInfo aInfo( _rEvent.Error );
1016 if ( aInfo.isValid() )
1017 next = _rEvent.Error;
1018 else
1019 {
1020 // a non-SQL exception happened
1021 Exception aException;
1022 OSL_VERIFY( _rEvent.Error >>= aException );
1023 SQLContext aContext(aException.Message, aException.Context, {}, 0, {},
1024 _rEvent.Error.getValueTypeName());
1025 next <<= aContext;
1026 }
1027 SQLContext aError(DBA_RES(STR_ERROR_OCCURRED_WHILE_COPYING), *this, {}, 0, next, {});
1028
1029 ::rtl::Reference< ::comphelper::OInteractionRequest > xRequest( new ::comphelper::OInteractionRequest( Any( aError ) ) );
1030
1031 ::rtl::Reference< ::comphelper::OInteractionApprove > xYes = new ::comphelper::OInteractionApprove;
1032 xRequest->addContinuation( xYes );
1033 xRequest->addContinuation( new ::comphelper::OInteractionDisapprove );
1034
1035 OSL_ENSURE( m_xInteractionHandler.is(),
1036 "CopyTableWizard::impl_processCopyError_nothrow: we always should have an interaction handler!" );
1037 if ( m_xInteractionHandler.is() )
1038 m_xInteractionHandler->handle( xRequest );
1039
1040 if ( xYes->wasSelected() )
1041 // continue copying
1042 return true;
1043 }
1044 catch( const Exception& )
1045 {
1046 DBG_UNHANDLED_EXCEPTION("dbaccess");
1047 }
1048
1049 // cancel copying
1050 return false;
1051 }
1052
impl_copyRows_throw(const Reference<XResultSet> & _rxSourceResultSet,const Reference<XPropertySet> & _rxDestTable)1053 void CopyTableWizard::impl_copyRows_throw( const Reference< XResultSet >& _rxSourceResultSet,
1054 const Reference< XPropertySet >& _rxDestTable )
1055 {
1056 OSL_PRECOND( m_xDestConnection.is(), "CopyTableWizard::impl_copyRows_throw: illegal call!" );
1057 if ( !m_xDestConnection.is() )
1058 throw RuntimeException( u"m_xDestConnection is set to null, CopyTableWizard::impl_copyRows_throw: illegal call!"_ustr, *this );
1059
1060 Reference< XDatabaseMetaData > xDestMetaData( m_xDestConnection->getMetaData(), UNO_SET_THROW );
1061
1062 const OCopyTableWizard& rWizard = impl_getDialog_throw();
1063 ODatabaseExport::TPositions aColumnPositions = rWizard.GetColumnPositions();
1064 const bool bShouldCreatePrimaryKey = rWizard.shouldCreatePrimaryKey();
1065
1066 Reference< XRow > xRow ( _rxSourceResultSet, UNO_QUERY_THROW );
1067 Reference< XRowLocate > xRowLocate ( _rxSourceResultSet, UNO_QUERY_THROW );
1068
1069 Reference< XResultSetMetaDataSupplier > xSuppResMeta( _rxSourceResultSet, UNO_QUERY_THROW );
1070 Reference< XResultSetMetaData> xMeta( xSuppResMeta->getMetaData() );
1071
1072 // we need a vector which all types
1073 sal_Int32 nCount = xMeta->getColumnCount();
1074 std::vector< sal_Int32 > aSourceColTypes;
1075 aSourceColTypes.reserve( nCount + 1 );
1076 aSourceColTypes.push_back( -1 ); // just to avoid an every time i-1 call
1077
1078 std::vector< sal_Int32 > aSourcePrec;
1079 aSourcePrec.reserve( nCount + 1 );
1080 aSourcePrec.push_back( -1 ); // just to avoid an every time i-1 call
1081
1082 for ( sal_Int32 k=1; k <= nCount; ++k )
1083 {
1084 aSourceColTypes.push_back( xMeta->getColumnType( k ) );
1085 aSourcePrec.push_back( xMeta->getPrecision( k ) );
1086 }
1087
1088 // now create, fill and execute the prepared statement
1089 Reference< XPreparedStatement > xStatement( ODatabaseExport::createPreparedStatement( xDestMetaData, _rxDestTable, aColumnPositions ), UNO_SET_THROW );
1090 Reference< XParameters > xStatementParams( xStatement, UNO_QUERY_THROW );
1091
1092 const bool bSelectedRecordsOnly = m_aSourceSelection.hasElements();
1093 const Any* pSelectedRow = m_aSourceSelection.begin();
1094 const Any* pSelEnd = m_aSourceSelection.end();
1095
1096 sal_Int32 nRowCount = 0;
1097 bool bContinue = false;
1098
1099 CopyTableRowEvent aCopyEvent;
1100 aCopyEvent.Source = *this;
1101 aCopyEvent.SourceData = _rxSourceResultSet;
1102
1103 do // loop as long as there are more rows or the selection ends
1104 {
1105 bContinue = false;
1106 if ( bSelectedRecordsOnly )
1107 {
1108 if ( pSelectedRow != pSelEnd )
1109 {
1110 if ( m_bSourceSelectionBookmarks )
1111 {
1112 bContinue = xRowLocate->moveToBookmark( *pSelectedRow );
1113 }
1114 else
1115 {
1116 sal_Int32 nPos = 0;
1117 OSL_VERIFY( *pSelectedRow >>= nPos );
1118 bContinue = _rxSourceResultSet->absolute( nPos );
1119 }
1120 ++pSelectedRow;
1121 }
1122 }
1123 else
1124 bContinue = _rxSourceResultSet->next();
1125
1126 if ( !bContinue )
1127 {
1128 break;
1129 }
1130
1131 ++nRowCount;
1132
1133 aCopyEvent.Error.clear();
1134 try
1135 {
1136 bool bInsertedPrimaryKey = false;
1137 // notify listeners
1138 m_aCopyTableListeners.notifyEach( &XCopyTableListener::copyingRow, aCopyEvent );
1139
1140 sal_Int32 nSourceColumn( 1 );
1141 ValueTransfer aTransfer( aSourceColTypes, xRow, xStatementParams );
1142
1143 for ( auto const& rColumnPos : aColumnPositions )
1144 {
1145 sal_Int32 nDestColumn = rColumnPos.first;
1146 if ( nDestColumn == COLUMN_POSITION_NOT_FOUND )
1147 {
1148 ++nSourceColumn;
1149 // otherwise we don't get the correct value when only the 2nd source column was selected
1150 continue;
1151 }
1152
1153 if ( bShouldCreatePrimaryKey && !bInsertedPrimaryKey )
1154 {
1155 xStatementParams->setInt( 1, nRowCount );
1156 ++nSourceColumn;
1157 bInsertedPrimaryKey = true;
1158 continue;
1159 }
1160
1161 if ( ( nSourceColumn < 1 ) || ( o3tl::make_unsigned(nSourceColumn) >= aSourceColTypes.size() ) )
1162 { // ( we have to check here against 1 because the parameters are 1 based)
1163 ::dbtools::throwSQLException(u"Internal error: invalid column type index."_ustr,
1164 ::dbtools::StandardSQLState::INVALID_DESCRIPTOR_INDEX, *this);
1165 }
1166
1167 switch ( aSourceColTypes[ nSourceColumn ] )
1168 {
1169 case DataType::DOUBLE:
1170 case DataType::REAL:
1171 aTransfer.transferValue( nSourceColumn, nDestColumn, &XRow::getDouble, &XParameters::setDouble );
1172 break;
1173
1174 case DataType::CHAR:
1175 case DataType::VARCHAR:
1176 case DataType::LONGVARCHAR:
1177 case DataType::DECIMAL:
1178 case DataType::NUMERIC:
1179 aTransfer.transferComplexValue( nSourceColumn, nDestColumn, &XRow::getString, &XParameters::setString );
1180 break;
1181
1182 case DataType::BIGINT:
1183 aTransfer.transferValue( nSourceColumn, nDestColumn, &XRow::getLong, &XParameters::setLong );
1184 break;
1185
1186 case DataType::FLOAT:
1187 aTransfer.transferValue( nSourceColumn, nDestColumn, &XRow::getFloat, &XParameters::setFloat );
1188 break;
1189
1190 case DataType::LONGVARBINARY:
1191 case DataType::BINARY:
1192 case DataType::VARBINARY:
1193 aTransfer.transferComplexValue( nSourceColumn, nDestColumn, &XRow::getBytes, &XParameters::setBytes );
1194 break;
1195
1196 case DataType::DATE:
1197 aTransfer.transferComplexValue( nSourceColumn, nDestColumn, &XRow::getDate, &XParameters::setDate );
1198 break;
1199
1200 case DataType::TIME:
1201 aTransfer.transferComplexValue( nSourceColumn, nDestColumn, &XRow::getTime, &XParameters::setTime );
1202 break;
1203
1204 case DataType::TIMESTAMP:
1205 aTransfer.transferComplexValue( nSourceColumn, nDestColumn, &XRow::getTimestamp, &XParameters::setTimestamp );
1206 break;
1207
1208 case DataType::BIT:
1209 if ( aSourcePrec[nSourceColumn] > 1 )
1210 {
1211 aTransfer.transferComplexValue( nSourceColumn, nDestColumn, &XRow::getBytes, &XParameters::setBytes );
1212 break;
1213 }
1214 [[fallthrough]];
1215 case DataType::BOOLEAN:
1216 aTransfer.transferValue( nSourceColumn, nDestColumn, &XRow::getBoolean, &XParameters::setBoolean );
1217 break;
1218
1219 case DataType::TINYINT:
1220 aTransfer.transferValue( nSourceColumn, nDestColumn, &XRow::getByte, &XParameters::setByte );
1221 break;
1222
1223 case DataType::SMALLINT:
1224 aTransfer.transferValue( nSourceColumn, nDestColumn, &XRow::getShort, &XParameters::setShort );
1225 break;
1226
1227 case DataType::INTEGER:
1228 aTransfer.transferValue( nSourceColumn, nDestColumn, &XRow::getInt, &XParameters::setInt );
1229 break;
1230
1231 case DataType::BLOB:
1232 aTransfer.transferComplexValue( nSourceColumn, nDestColumn, &XRow::getBlob, &XParameters::setBlob );
1233 break;
1234
1235 case DataType::CLOB:
1236 aTransfer.transferComplexValue( nSourceColumn, nDestColumn, &XRow::getClob, &XParameters::setClob );
1237 break;
1238
1239 default:
1240 {
1241 OUString aMessage( DBA_RES( STR_CTW_UNSUPPORTED_COLUMN_TYPE ) );
1242
1243 aMessage = aMessage.replaceFirst( "$type$", OUString::number( aSourceColTypes[ nSourceColumn ] ) );
1244 aMessage = aMessage.replaceFirst( "$pos$", OUString::number( nSourceColumn ) );
1245
1246 ::dbtools::throwSQLException(
1247 aMessage,
1248 ::dbtools::StandardSQLState::INVALID_SQL_DATA_TYPE,
1249 *this
1250 );
1251 }
1252 }
1253 ++nSourceColumn;
1254 }
1255 xStatement->executeUpdate();
1256
1257 // notify listeners
1258 m_aCopyTableListeners.notifyEach( &XCopyTableListener::copiedRow, aCopyEvent );
1259 }
1260 catch( const Exception& )
1261 {
1262 TOOLS_WARN_EXCEPTION("dbaccess", "");
1263 aCopyEvent.Error = ::cppu::getCaughtException();
1264 }
1265
1266 if ( aCopyEvent.Error.hasValue() )
1267 bContinue = impl_processCopyError_nothrow( aCopyEvent );
1268 }
1269 while( bContinue );
1270 }
1271
impl_doCopy_nothrow()1272 void CopyTableWizard::impl_doCopy_nothrow()
1273 {
1274 Any aError;
1275
1276 try
1277 {
1278 OCopyTableWizard& rWizard( impl_getDialog_throw() );
1279
1280 weld::WaitObject aWO(rWizard.getDialog());
1281 Reference< XPropertySet > xTable;
1282
1283 switch ( rWizard.getOperation() )
1284 {
1285 case CopyTableOperation::CopyDefinitionOnly:
1286 case CopyTableOperation::CopyDefinitionAndData:
1287 {
1288 xTable = rWizard.createTable();
1289
1290 if( !xTable.is() )
1291 {
1292 SAL_WARN("dbaccess.ui", "CopyTableWizard::impl_doCopy_nothrow: createTable should throw here, shouldn't it?" );
1293 break;
1294 }
1295
1296 if( CopyTableOperation::CopyDefinitionOnly == rWizard.getOperation() )
1297 break;
1298
1299 [[fallthrough]];
1300 }
1301
1302 case CopyTableOperation::AppendData:
1303 {
1304 // note that the CopyDefinitionAndData case falls through to here.
1305 assert((rWizard.getOperation() == CopyTableOperation::CopyDefinitionAndData) ||
1306 (rWizard.getOperation() == CopyTableOperation::AppendData));
1307 assert((rWizard.getOperation() == CopyTableOperation::CopyDefinitionAndData) == xTable.is());
1308 if ( !xTable.is() )
1309 {
1310 assert(rWizard.getOperation() == CopyTableOperation::AppendData);
1311 xTable = rWizard.getTable();
1312 if ( !xTable.is() )
1313 {
1314 SAL_WARN("dbaccess.ui", "CopyTableWizard::impl_doCopy_nothrow: getTable should throw here, shouldn't it?" );
1315 break;
1316 }
1317 }
1318
1319 ::utl::SharedUNOComponent< XPreparedStatement > xSourceStatement;
1320 ::utl::SharedUNOComponent< XResultSet > xSourceResultSet;
1321
1322 if ( m_xSourceResultSet.is() )
1323 {
1324 xSourceResultSet.reset( m_xSourceResultSet, ::utl::SharedUNOComponent< XResultSet >::NoTakeOwnership );
1325 }
1326 else
1327 {
1328 const bool bIsSameConnection = ( m_xSourceConnection.getTyped() == m_xDestConnection.getTyped() );
1329 const bool bIsTable = ( CommandType::TABLE == m_nCommandType );
1330 bool bDone = false;
1331 if ( bIsSameConnection && bIsTable )
1332 {
1333 // try whether the server supports copying via SQL
1334 try
1335 {
1336 m_xDestConnection->createStatement()->executeUpdate( impl_getServerSideCopyStatement_throw(xTable) );
1337 bDone = true;
1338 }
1339 catch( const Exception& )
1340 {
1341 // this is allowed.
1342 }
1343 }
1344
1345 if ( !bDone )
1346 {
1347 xSourceStatement.set( impl_createSourceStatement_throw(), UNO_SET_THROW );
1348 xSourceResultSet.set( xSourceStatement->executeQuery(), UNO_SET_THROW );
1349 }
1350 }
1351
1352 if ( xSourceResultSet.is() )
1353 impl_copyRows_throw( xSourceResultSet, xTable );
1354
1355 // tdf#119962
1356 const Reference< XDatabaseMetaData > xDestMetaData( m_xDestConnection->getMetaData(), UNO_SET_THROW );
1357 OUString sDatabaseDest = xDestMetaData->getDatabaseProductName().toAsciiLowerCase();
1358 // If we created a new primary key, then it won't necessarily be an IDENTITY column
1359 const bool bShouldCreatePrimaryKey = rWizard.shouldCreatePrimaryKey();
1360 if ( !bShouldCreatePrimaryKey && (sDatabaseDest.indexOf("firebird") != -1) )
1361 {
1362 const OUString sComposedTableName = ::dbtools::composeTableName( xDestMetaData, xTable, ::dbtools::EComposeRule::InDataManipulation, true );
1363
1364 OUString aSchema,aTable;
1365 xTable->getPropertyValue(u"SchemaName"_ustr) >>= aSchema;
1366 xTable->getPropertyValue(u"Name"_ustr) >>= aTable;
1367 Any aCatalog = xTable->getPropertyValue(u"CatalogName"_ustr);
1368
1369 const Reference< XResultSet > xResultPKCL(xDestMetaData->getPrimaryKeys(aCatalog,aSchema,aTable));
1370 Reference< XRow > xRowPKCL(xResultPKCL, UNO_QUERY_THROW);
1371 OUString sPKCL;
1372 if ( xRowPKCL.is() )
1373 {
1374 if (xResultPKCL->next())
1375 {
1376 sPKCL = xRowPKCL->getString(4);
1377 }
1378 }
1379
1380 if (!sPKCL.isEmpty())
1381 {
1382 OUString strSql = "SELECT MAX(\"" + sPKCL + "\") FROM " + sComposedTableName;
1383
1384 Reference< XResultSet > xResultMAXNUM(m_xDestConnection->createStatement()->executeQuery(strSql));
1385 Reference< XRow > xRow(xResultMAXNUM, UNO_QUERY_THROW);
1386
1387 sal_Int64 maxVal = -1L;
1388 if (xResultMAXNUM->next())
1389 {
1390 maxVal = xRow->getLong(1);
1391 }
1392
1393 if (maxVal > 0L)
1394 {
1395 strSql = "ALTER TABLE " + sComposedTableName + " ALTER \"" + sPKCL + "\" RESTART WITH " + OUString::number(maxVal + 1);
1396
1397 m_xDestConnection->createStatement()->execute(strSql);
1398 }
1399 }
1400 }
1401 }
1402 break;
1403
1404 case CopyTableOperation::CreateAsView:
1405 rWizard.createView();
1406 break;
1407
1408 default:
1409 SAL_WARN("dbaccess.ui", "CopyTableWizard::impl_doCopy_nothrow: What operation, please?" );
1410 break;
1411 }
1412 }
1413 catch( const Exception& )
1414 {
1415 aError = ::cppu::getCaughtException();
1416 SAL_WARN("dbaccess", exceptionToString(aError));
1417
1418 // silence the error of the user cancelling the parameter's dialog
1419 SQLException aSQLError;
1420 if ( ( aError >>= aSQLError ) && ( aSQLError.ErrorCode == ::dbtools::ParameterInteractionCancelled ) )
1421 {
1422 aError.clear();
1423 m_nOverrideExecutionResult = RET_CANCEL;
1424 }
1425 }
1426
1427 if ( aError.hasValue() && m_xInteractionHandler.is() )
1428 {
1429 try
1430 {
1431 ::rtl::Reference< ::comphelper::OInteractionRequest > xRequest( new ::comphelper::OInteractionRequest( aError ) );
1432 m_xInteractionHandler->handle( xRequest );
1433 }
1434 catch( const Exception& )
1435 {
1436 DBG_UNHANDLED_EXCEPTION("dbaccess");
1437 }
1438 }
1439 }
1440
impl_getServerSideCopyStatement_throw(const Reference<XPropertySet> & _xTable)1441 OUString CopyTableWizard::impl_getServerSideCopyStatement_throw(const Reference< XPropertySet >& _xTable)
1442 {
1443 const Reference<XColumnsSupplier> xDestColsSup(_xTable,UNO_QUERY_THROW);
1444 const Sequence< OUString> aDestColumnNames = xDestColsSup->getColumns()->getElementNames();
1445 const Reference< XDatabaseMetaData > xDestMetaData( m_xDestConnection->getMetaData(), UNO_SET_THROW );
1446 const OUString sQuote = xDestMetaData->getIdentifierQuoteString();
1447 OUStringBuffer sColumns;
1448 // 1st check if the columns matching
1449 for ( auto const & rColumnPositionPair : impl_getDialog_throw().GetColumnPositions() )
1450 {
1451 if ( COLUMN_POSITION_NOT_FOUND != rColumnPositionPair.second )
1452 {
1453 if ( !sColumns.isEmpty() )
1454 sColumns.append(",");
1455 sColumns.append(sQuote + aDestColumnNames[rColumnPositionPair.second - 1] + sQuote);
1456 }
1457 }
1458 const OUString sComposedTableName = ::dbtools::composeTableName( xDestMetaData, _xTable, ::dbtools::EComposeRule::InDataManipulation, true );
1459 OUString sSql("INSERT INTO " + sComposedTableName + " ( " + sColumns + " ) " + m_pSourceObject->getSelectStatement());
1460
1461 return sSql;
1462 }
1463
initialize(const Sequence<Any> & _rArguments)1464 void SAL_CALL CopyTableWizard::initialize( const Sequence< Any >& _rArguments )
1465 {
1466 ::osl::MutexGuard aGuard( m_aMutex );
1467 if ( isInitialized() )
1468 throw AlreadyInitializedException( OUString(), *this );
1469
1470 sal_Int32 nArgCount( _rArguments.getLength() );
1471 if ( ( nArgCount != 2 ) && ( nArgCount != 3 ) )
1472 throw IllegalArgumentException(
1473 DBA_RES( STR_CTW_ILLEGAL_PARAMETER_COUNT ),
1474 *this,
1475 1
1476 );
1477
1478 try
1479 {
1480 if ( nArgCount == 3 )
1481 { // ->createWithInteractionHandler
1482 if ( !( _rArguments[2] >>= m_xInteractionHandler ) )
1483 throw IllegalArgumentException(
1484 DBA_RES( STR_CTW_ERROR_INVALID_INTERACTIONHANDLER ),
1485 *this,
1486 3
1487 );
1488 }
1489 if ( !m_xInteractionHandler.is() )
1490 m_xInteractionHandler = InteractionHandler::createWithParent(m_xContext, nullptr);
1491
1492 Reference< XInteractionHandler > xSourceDocHandler;
1493 Reference< XPropertySet > xSourceDescriptor( impl_ensureDataAccessDescriptor_throw( _rArguments, 0, m_xSourceConnection, xSourceDocHandler ) );
1494 impl_checkForUnsupportedSettings_throw( xSourceDescriptor );
1495 m_pSourceObject = impl_extractSourceObject_throw( xSourceDescriptor, m_nCommandType );
1496 impl_extractSourceResultSet_throw( xSourceDescriptor );
1497
1498 Reference< XInteractionHandler > xDestDocHandler;
1499 impl_ensureDataAccessDescriptor_throw( _rArguments, 1, m_xDestConnection, xDestDocHandler );
1500
1501 if ( xDestDocHandler.is() && !m_xInteractionHandler.is() )
1502 m_xInteractionHandler = std::move(xDestDocHandler);
1503
1504 Reference< XPropertySet > xInteractionHandler(m_xInteractionHandler, UNO_QUERY);
1505 if (xInteractionHandler.is())
1506 {
1507 Any aParentWindow(xInteractionHandler->getPropertyValue(u"ParentWindow"_ustr));
1508 aParentWindow >>= m_xParent;
1509 }
1510 }
1511 catch( const RuntimeException& ) { throw; }
1512 catch( const SQLException& ) { throw; }
1513 catch( const Exception& )
1514 {
1515 throw WrappedTargetException(
1516 DBA_RES( STR_CTW_ERROR_DURING_INITIALIZATION ),
1517 *this,
1518 ::cppu::getCaughtException()
1519 );
1520 }
1521 }
1522
getInfoHelper()1523 ::cppu::IPropertyArrayHelper& CopyTableWizard::getInfoHelper()
1524 {
1525 return *getArrayHelper();
1526 }
1527
createArrayHelper() const1528 ::cppu::IPropertyArrayHelper* CopyTableWizard::createArrayHelper( ) const
1529 {
1530 Sequence< Property > aProps;
1531 describeProperties( aProps );
1532 return new ::cppu::OPropertyArrayHelper( aProps );
1533 }
1534
createDialog(const css::uno::Reference<css::awt::XWindow> & rParent)1535 std::unique_ptr<weld::DialogController> CopyTableWizard::createDialog(const css::uno::Reference<css::awt::XWindow>& rParent)
1536 {
1537 OSL_PRECOND( isInitialized(), "CopyTableWizard::createDialog: not initialized!" );
1538 // this should have been prevented in ::execute already
1539
1540 auto xWizard = std::make_unique<OCopyTableWizard>(
1541 Application::GetFrameWeld(rParent),
1542 m_sDestinationTable,
1543 m_nOperation,
1544 *m_pSourceObject,
1545 m_xSourceConnection.getTyped(),
1546 m_xDestConnection.getTyped(),
1547 m_xContext,
1548 m_xInteractionHandler);
1549
1550 impl_attributesToDialog_nothrow(*xWizard);
1551
1552 return xWizard;
1553 }
1554
executedDialog(sal_Int16 _nExecutionResult)1555 void CopyTableWizard::executedDialog( sal_Int16 _nExecutionResult )
1556 {
1557 CopyTableWizard_DialogBase::executedDialog( _nExecutionResult );
1558
1559 if ( _nExecutionResult == RET_OK )
1560 impl_doCopy_nothrow();
1561
1562 // do this after impl_doCopy_nothrow: The attributes may change during copying, for instance
1563 // if the user entered an unqualified table name
1564 impl_dialogToAttributes_nothrow( impl_getDialog_throw() );
1565 }
1566
1567 } // namespace dbaui
1568
1569 extern "C" SAL_DLLPUBLIC_EXPORT css::uno::XInterface*
org_openoffice_comp_dbu_CopyTableWizard_get_implementation(css::uno::XComponentContext * context,css::uno::Sequence<css::uno::Any> const &)1570 org_openoffice_comp_dbu_CopyTableWizard_get_implementation(
1571 css::uno::XComponentContext* context, css::uno::Sequence<css::uno::Any> const& )
1572 {
1573 return cppu::acquire(new ::dbaui::CopyTableWizard(context));
1574 }
1575
1576 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */
1577