1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
3 * This file is part of the LibreOffice project.
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/.
9 * This file incorporates work covered by the following license notice:
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 .
20 #include "DbAdminImpl.hxx"
23 #include <svl/poolitem.hxx>
24 #include <svl/itempool.hxx>
25 #include <svl/stritem.hxx>
26 #include <svl/intitem.hxx>
27 #include <svl/eitem.hxx>
28 #include <IItemSetHelper.hxx>
29 #include <UITools.hxx>
30 #include <core_resource.hxx>
31 #include <strings.hrc>
32 #include <strings.hxx>
33 #include <dsitems.hxx>
34 #include "dsnItem.hxx"
35 #include "optionalboolitem.hxx"
36 #include <stringlistitem.hxx>
37 #include <OAuthenticationContinuation.hxx>
39 #include <com/sun/star/beans/PropertyAttribute.hpp>
40 #include <com/sun/star/frame/XStorable.hpp>
41 #include <com/sun/star/sdb/DatabaseContext.hpp>
42 #include <com/sun/star/sdb/SQLContext.hpp>
43 #include <com/sun/star/sdbc/ConnectionPool.hpp>
44 #include <com/sun/star/sdbc/XDriver.hpp>
45 #include <com/sun/star/task/InteractionHandler.hpp>
46 #include <com/sun/star/ucb/AuthenticationRequest.hpp>
48 #include <comphelper/interaction.hxx>
49 #include <comphelper/sequence.hxx>
50 #include <connectivity/DriversConfig.hxx>
51 #include <connectivity/dbexception.hxx>
52 #include <osl/file.hxx>
53 #include <comphelper/diagnose_ex.hxx>
54 #include <osl/diagnose.h>
55 #include <sal/log.hxx>
56 #include <typelib/typedescription.hxx>
57 #include <vcl/svapp.hxx>
58 #include <vcl/stdtext.hxx>
59 #include <vcl/weld.hxx>
64 #include <o3tl/functional.hxx>
65 #include <comphelper/string.hxx>
69 using namespace ::dbtools
;
70 using namespace com::sun::star::uno
;
71 using namespace com::sun::star
;
72 using namespace com::sun::star::ucb
;
73 using namespace com::sun::star::task
;
74 using namespace com::sun::star::sdbc
;
75 using namespace com::sun::star::sdb
;
76 using namespace com::sun::star::lang
;
77 using namespace com::sun::star::beans
;
78 using namespace com::sun::star::util
;
79 using namespace com::sun::star::container
;
80 using namespace com::sun::star::frame
;
84 bool implCheckItemType( SfxItemSet
const & _rSet
, const sal_uInt16 _nId
, const std::function
<bool ( const SfxPoolItem
* )>& isItemType
)
86 bool bCorrectType
= false;
88 SfxItemPool
* pPool
= _rSet
.GetPool();
89 OSL_ENSURE( pPool
, "implCheckItemType: invalid item pool!" );
92 const SfxPoolItem
& rDefItem
= pPool
->GetDefaultItem( _nId
);
93 bCorrectType
= isItemType(&rDefItem
);
98 void lcl_putProperty(const Reference
< XPropertySet
>& _rxSet
, const OUString
& _rName
, const Any
& _rValue
)
103 _rxSet
->setPropertyValue(_rName
, _rValue
);
107 SAL_WARN("dbaccess", "ODbAdminDialog::implTranslateProperty: could not set the property "
113 OUString
lcl_createHostWithPort(const SfxStringItem
* _pHostName
,const SfxInt32Item
* _pPortNumber
)
117 if ( _pHostName
&& _pHostName
->GetValue().getLength() )
118 sNewUrl
= _pHostName
->GetValue();
122 sNewUrl
+= ":" + OUString::number(_pPortNumber
->GetValue());
129 // ODbDataSourceAdministrationHelper
130 ODbDataSourceAdministrationHelper::ODbDataSourceAdministrationHelper(const Reference
< XComponentContext
>& _xORB
, weld::Window
* pParent
, weld::Window
* pTopParent
, IItemSetHelper
* _pItemSetHelper
)
133 , m_pItemSetHelper(_pItemSetHelper
)
135 /// initialize the property translation map
136 // direct properties of a data source
137 m_aDirectPropTranslator
.emplace( DSID_CONNECTURL
, PROPERTY_URL
);
138 m_aDirectPropTranslator
.emplace( DSID_NAME
, PROPERTY_NAME
);
139 m_aDirectPropTranslator
.emplace( DSID_USER
, PROPERTY_USER
);
140 m_aDirectPropTranslator
.emplace( DSID_PASSWORD
, PROPERTY_PASSWORD
);
141 m_aDirectPropTranslator
.emplace( DSID_PASSWORDREQUIRED
, PROPERTY_ISPASSWORDREQUIRED
);
142 m_aDirectPropTranslator
.emplace( DSID_TABLEFILTER
, PROPERTY_TABLEFILTER
);
143 m_aDirectPropTranslator
.emplace( DSID_READONLY
, PROPERTY_ISREADONLY
);
144 m_aDirectPropTranslator
.emplace( DSID_SUPPRESSVERSIONCL
, PROPERTY_SUPPRESSVERSIONCL
);
146 // implicit properties, to be found in the direct property "Info"
147 m_aIndirectPropTranslator
.emplace( DSID_JDBCDRIVERCLASS
, INFO_JDBCDRIVERCLASS
);
148 m_aIndirectPropTranslator
.emplace( DSID_TEXTFILEEXTENSION
, INFO_TEXTFILEEXTENSION
);
149 m_aIndirectPropTranslator
.emplace( DSID_CHARSET
, INFO_CHARSET
);
150 m_aIndirectPropTranslator
.emplace( DSID_TEXTFILEHEADER
, INFO_TEXTFILEHEADER
);
151 m_aIndirectPropTranslator
.emplace( DSID_FIELDDELIMITER
, INFO_FIELDDELIMITER
);
152 m_aIndirectPropTranslator
.emplace( DSID_TEXTDELIMITER
, INFO_TEXTDELIMITER
);
153 m_aIndirectPropTranslator
.emplace( DSID_DECIMALDELIMITER
, INFO_DECIMALDELIMITER
);
154 m_aIndirectPropTranslator
.emplace( DSID_THOUSANDSDELIMITER
, INFO_THOUSANDSDELIMITER
);
155 m_aIndirectPropTranslator
.emplace( DSID_SHOWDELETEDROWS
, INFO_SHOWDELETEDROWS
);
156 m_aIndirectPropTranslator
.emplace( DSID_ALLOWLONGTABLENAMES
, INFO_ALLOWLONGTABLENAMES
);
157 m_aIndirectPropTranslator
.emplace( DSID_ADDITIONALOPTIONS
, INFO_ADDITIONALOPTIONS
);
158 m_aIndirectPropTranslator
.emplace( DSID_SQL92CHECK
, PROPERTY_ENABLESQL92CHECK
);
159 m_aIndirectPropTranslator
.emplace( DSID_AUTOINCREMENTVALUE
, PROPERTY_AUTOINCREMENTCREATION
);
160 m_aIndirectPropTranslator
.emplace( DSID_AUTORETRIEVEVALUE
, INFO_AUTORETRIEVEVALUE
);
161 m_aIndirectPropTranslator
.emplace( DSID_AUTORETRIEVEENABLED
, INFO_AUTORETRIEVEENABLED
);
162 m_aIndirectPropTranslator
.emplace( DSID_APPEND_TABLE_ALIAS
, INFO_APPEND_TABLE_ALIAS
);
163 m_aIndirectPropTranslator
.emplace( DSID_AS_BEFORE_CORRNAME
, INFO_AS_BEFORE_CORRELATION_NAME
);
164 m_aIndirectPropTranslator
.emplace( DSID_CHECK_REQUIRED_FIELDS
, INFO_FORMS_CHECK_REQUIRED_FIELDS
);
165 m_aIndirectPropTranslator
.emplace( DSID_ESCAPE_DATETIME
, INFO_ESCAPE_DATETIME
);
166 m_aIndirectPropTranslator
.emplace( DSID_PRIMARY_KEY_SUPPORT
, OUString("PrimaryKeySupport") );
167 m_aIndirectPropTranslator
.emplace( DSID_PARAMETERNAMESUBST
, INFO_PARAMETERNAMESUBST
);
168 m_aIndirectPropTranslator
.emplace( DSID_IGNOREDRIVER_PRIV
, INFO_IGNOREDRIVER_PRIV
);
169 m_aIndirectPropTranslator
.emplace( DSID_BOOLEANCOMPARISON
, PROPERTY_BOOLEANCOMPARISONMODE
);
170 m_aIndirectPropTranslator
.emplace( DSID_ENABLEOUTERJOIN
, PROPERTY_ENABLEOUTERJOIN
);
171 m_aIndirectPropTranslator
.emplace( DSID_CATALOG
, PROPERTY_USECATALOGINSELECT
);
172 m_aIndirectPropTranslator
.emplace( DSID_SCHEMA
, PROPERTY_USESCHEMAINSELECT
);
173 m_aIndirectPropTranslator
.emplace( DSID_INDEXAPPENDIX
, OUString("AddIndexAppendix") );
174 m_aIndirectPropTranslator
.emplace( DSID_DOSLINEENDS
, OUString("PreferDosLikeLineEnds") );
175 m_aIndirectPropTranslator
.emplace( DSID_CONN_SOCKET
, OUString("LocalSocket") );
176 m_aIndirectPropTranslator
.emplace( DSID_NAMED_PIPE
, OUString("NamedPipe") );
177 m_aIndirectPropTranslator
.emplace( DSID_RESPECTRESULTSETTYPE
, OUString("RespectDriverResultSetType") );
178 m_aIndirectPropTranslator
.emplace( DSID_MAX_ROW_SCAN
, OUString("MaxRowScan") );
180 // extra settings for ODBC
181 m_aIndirectPropTranslator
.emplace( DSID_USECATALOG
, INFO_USECATALOG
);
182 // extra settings for an LDAP address book
183 m_aIndirectPropTranslator
.emplace( DSID_CONN_LDAP_BASEDN
, INFO_CONN_LDAP_BASEDN
);
184 m_aIndirectPropTranslator
.emplace( DSID_CONN_LDAP_ROWCOUNT
, INFO_CONN_LDAP_ROWCOUNT
);
185 m_aIndirectPropTranslator
.emplace( DSID_CONN_LDAP_USESSL
, OUString("UseSSL") );
186 m_aIndirectPropTranslator
.emplace( DSID_DOCUMENT_URL
, PROPERTY_URL
);
189 m_aIndirectPropTranslator
.emplace( DSID_IGNORECURRENCY
, OUString("IgnoreCurrency") );
193 m_xDatabaseContext
= DatabaseContext::create(m_xContext
);
195 catch(const Exception
&)
197 ShowServiceNotAvailableError(pTopParent
, u
"com.sun.star.sdb.DatabaseContext", true);
201 bool ODbDataSourceAdministrationHelper::getCurrentSettings(Sequence
< PropertyValue
>& _rDriverParam
)
203 OSL_ENSURE(m_pItemSetHelper
->getOutputSet(), "ODbDataSourceAdministrationHelper::getCurrentSettings : not to be called without an example set!");
204 if (!m_pItemSetHelper
->getOutputSet())
207 std::vector
< PropertyValue
> aReturn
;
208 // collecting this in a vector because it has a push_back, in opposite to sequences
210 // user: DSID_USER -> "user"
211 const SfxStringItem
* pUser
= m_pItemSetHelper
->getOutputSet()->GetItem
<SfxStringItem
>(DSID_USER
);
212 if (pUser
&& pUser
->GetValue().getLength())
213 aReturn
.emplace_back( "user", 0,
214 Any(pUser
->GetValue()), PropertyState_DIRECT_VALUE
);
216 // check if the connection type requires a password
217 if (hasAuthentication(*m_pItemSetHelper
->getOutputSet()))
219 // password: DSID_PASSWORD -> password
220 const SfxStringItem
* pPassword
= m_pItemSetHelper
->getOutputSet()->GetItem
<SfxStringItem
>(DSID_PASSWORD
);
221 OUString sPassword
= pPassword
? pPassword
->GetValue() : OUString();
222 const SfxBoolItem
* pPasswordRequired
= m_pItemSetHelper
->getOutputSet()->GetItem
<SfxBoolItem
>(DSID_PASSWORDREQUIRED
);
223 // if the set does not contain a password, but the item set says it requires one, ask the user
224 if ((!pPassword
|| !pPassword
->GetValue().getLength()) && (pPasswordRequired
&& pPasswordRequired
->GetValue()))
226 const SfxStringItem
* pName
= m_pItemSetHelper
->getOutputSet()->GetItem
<SfxStringItem
>(DSID_NAME
);
228 Reference
< XModel
> xModel( getDataSourceOrModel( m_xDatasource
), UNO_QUERY_THROW
);
229 ::comphelper::NamedValueCollection
aArgs( xModel
->getArgs() );
230 Reference
< XInteractionHandler
> xHandler( aArgs
.getOrDefault( "InteractionHandler", Reference
< XInteractionHandler
>() ) );
232 if ( !xHandler
.is() )
234 // instantiate the default SDB interaction handler
235 xHandler
= task::InteractionHandler::createWithParent(m_xContext
, m_pParent
->GetXWindow());
238 OUString sName
= pName
? pName
->GetValue() : OUString();
239 OUString
sLoginRequest(DBA_RES(STR_ENTER_CONNECTION_PASSWORD
));
240 OUString sTemp
= sName
;
241 sName
= ::dbaui::getStrippedDatabaseName(nullptr,sTemp
);
242 if ( !sName
.isEmpty() )
243 sLoginRequest
= sLoginRequest
.replaceAll("$name$", sName
);
246 sLoginRequest
= sLoginRequest
.replaceAll("\"$name$\"", "");
247 // ensure that in other languages the string will be deleted
248 sLoginRequest
= sLoginRequest
.replaceAll("$name$", "");
252 AuthenticationRequest aRequest
;
253 aRequest
.ServerName
= sName
;
254 aRequest
.Diagnostic
= sLoginRequest
;
255 aRequest
.HasRealm
= false;
257 aRequest
.HasUserName
= pUser
!= nullptr;
258 aRequest
.UserName
= pUser
? pUser
->GetValue() : OUString();
259 aRequest
.HasPassword
= true;
261 aRequest
.HasAccount
= false;
264 rtl::Reference
<comphelper::OInteractionRequest
> pRequest
= new comphelper::OInteractionRequest(Any(aRequest
));
266 // build an interaction request
267 // two continuations (Ok and Cancel)
268 ::rtl::Reference
< comphelper::OInteractionAbort
> pAbort
= new comphelper::OInteractionAbort
;
269 ::rtl::Reference
< dbaccess::OAuthenticationContinuation
> pAuthenticate
= new dbaccess::OAuthenticationContinuation
;
270 pAuthenticate
->setCanChangeUserName( false );
271 pAuthenticate
->setRememberPassword( RememberAuthentication_SESSION
);
274 pRequest
->addContinuation(pAbort
);
275 pRequest
->addContinuation(pAuthenticate
);
277 // handle the request
280 SolarMutexGuard aSolarGuard
;
281 // release the mutex when calling the handler, it may need to lock the SolarMutex
282 xHandler
->handle(pRequest
);
286 DBG_UNHANDLED_EXCEPTION("dbaccess");
288 if (!pAuthenticate
->wasSelected())
291 sPassword
= pAuthenticate
->getPassword();
292 if (pAuthenticate
->getRememberPassword())
293 m_pItemSetHelper
->getWriteOutputSet()->Put(SfxStringItem(DSID_PASSWORD
, sPassword
));
296 if (!sPassword
.isEmpty())
297 aReturn
.emplace_back( "password", 0,
298 Any(sPassword
), PropertyState_DIRECT_VALUE
);
301 if ( !aReturn
.empty() )
302 _rDriverParam
= comphelper::containerToSequence(aReturn
);
304 // append all the other stuff (charset etc.)
305 fillDatasourceInfo(*m_pItemSetHelper
->getOutputSet(), _rDriverParam
);
310 void ODbDataSourceAdministrationHelper::successfullyConnected()
312 OSL_ENSURE(m_pItemSetHelper
->getOutputSet(), "ODbDataSourceAdministrationHelper::successfullyConnected: not to be called without an example set!");
313 if (!m_pItemSetHelper
->getOutputSet())
316 if (hasAuthentication(*m_pItemSetHelper
->getOutputSet()))
318 const SfxStringItem
* pPassword
= m_pItemSetHelper
->getOutputSet()->GetItem
<SfxStringItem
>(DSID_PASSWORD
);
319 if (pPassword
&& (0 != pPassword
->GetValue().getLength()))
321 OUString sPassword
= pPassword
->GetValue();
323 Reference
< XPropertySet
> xCurrentDatasource
= getCurrentDataSource();
324 lcl_putProperty(xCurrentDatasource
,m_aDirectPropTranslator
[DSID_PASSWORD
], Any(sPassword
));
329 void ODbDataSourceAdministrationHelper::clearPassword()
331 if (m_pItemSetHelper
->getWriteOutputSet())
332 m_pItemSetHelper
->getWriteOutputSet()->ClearItem(DSID_PASSWORD
);
335 std::pair
< Reference
<XConnection
>,bool> ODbDataSourceAdministrationHelper::createConnection()
337 std::pair
< Reference
<XConnection
>,bool> aRet
;
339 Sequence
< PropertyValue
> aConnectionParams
;
340 if ( getCurrentSettings(aConnectionParams
) )
343 // fill the table list with this connection information
344 SQLExceptionInfo aErrorInfo
;
347 weld::WaitObject
aWaitCursor(m_pParent
);
348 aRet
.first
= getDriver()->connect(getConnectionURL(), aConnectionParams
);
351 catch (const SQLContext
& e
) { aErrorInfo
= SQLExceptionInfo(e
); }
352 catch (const SQLWarning
& e
) { aErrorInfo
= SQLExceptionInfo(e
); }
353 catch (const SQLException
& e
) { aErrorInfo
= SQLExceptionInfo(e
); }
355 showError(aErrorInfo
,m_pParent
->GetXWindow(),getORB());
357 if ( aRet
.first
.is() )
358 successfullyConnected();// notify the admindlg to save the password
363 Reference
< XDriver
> ODbDataSourceAdministrationHelper::getDriver()
365 return getDriver(getConnectionURL());
368 Reference
< XDriver
> ODbDataSourceAdministrationHelper::getDriver(const OUString
& _sURL
)
370 // get the global DriverManager
371 Reference
< XConnectionPool
> xDriverManager
;
373 OUString sCurrentActionError
= DBA_RES(STR_COULDNOTCREATE_DRIVERMANAGER
);
374 sCurrentActionError
= sCurrentActionError
.replaceFirst("#servicename#", "com.sun.star.sdbc.ConnectionPool");
378 xDriverManager
.set( ConnectionPool::create( getORB() ) );
380 catch (const Exception
&)
382 css::uno::Any anyEx
= cppu::getCaughtException();
383 // wrap the exception into an SQLException
384 throw SQLException(sCurrentActionError
, getORB(), "S1000", 0, anyEx
);
387 Reference
< XDriver
> xDriver
= xDriverManager
->getDriverByURL(_sURL
);
390 sCurrentActionError
= DBA_RES(STR_NOREGISTEREDDRIVER
);
391 sCurrentActionError
= sCurrentActionError
.replaceFirst("#connurl#", _sURL
);
392 // will be caught and translated into an SQLContext exception
393 throw SQLException(sCurrentActionError
, getORB(), "S1000", 0, Any());
398 Reference
< XPropertySet
> const & ODbDataSourceAdministrationHelper::getCurrentDataSource()
400 if ( !m_xDatasource
.is() )
402 Reference
<XInterface
> xIn(m_aDataSourceOrName
,UNO_QUERY
);
405 OUString sCurrentDatasource
;
406 m_aDataSourceOrName
>>= sCurrentDatasource
;
407 OSL_ENSURE(!sCurrentDatasource
.isEmpty(),"No datasource name given!");
410 if ( m_xDatabaseContext
.is() )
411 m_xDatasource
.set(m_xDatabaseContext
->getByName(sCurrentDatasource
),UNO_QUERY
);
414 catch(const Exception
&)
418 m_xModel
.set(getDataSourceOrModel(xIn
),UNO_QUERY
);
420 m_xDatasource
.set(xIn
,UNO_QUERY
);
423 m_xDatasource
.set(getDataSourceOrModel(xIn
),UNO_QUERY
);
424 m_xModel
.set(xIn
,UNO_QUERY
);
428 OSL_ENSURE(m_xDatasource
.is(), "ODbDataSourceAdministrationHelper::getCurrentDataSource: no data source!");
429 return m_xDatasource
;
432 OUString
ODbDataSourceAdministrationHelper::getDatasourceType( const SfxItemSet
& _rSet
)
434 const SfxStringItem
* pConnectURL
= _rSet
.GetItem
<SfxStringItem
>(DSID_CONNECTURL
);
435 OSL_ENSURE( pConnectURL
, "ODbDataSourceAdministrationHelper::getDatasourceType: invalid items in the source set!" );
436 const DbuTypeCollectionItem
* pTypeCollection
= _rSet
.GetItem
<DbuTypeCollectionItem
>(DSID_TYPECOLLECTION
);
437 OSL_ENSURE(pTypeCollection
, "ODbDataSourceAdministrationHelper::getDatasourceType: invalid items in the source set!");
438 ::dbaccess::ODsnTypeCollection
* pCollection
= pTypeCollection
->getCollection();
439 return pCollection
->getType(pConnectURL
->GetValue());
442 bool ODbDataSourceAdministrationHelper::hasAuthentication(const SfxItemSet
& _rSet
)
444 return DataSourceMetaData::getAuthentication( getDatasourceType( _rSet
) ) != AuthNone
;
447 OUString
ODbDataSourceAdministrationHelper::getConnectionURL() const
451 OUString eType
= getDatasourceType(*m_pItemSetHelper
->getOutputSet());
453 const SfxStringItem
* pUrlItem
= m_pItemSetHelper
->getOutputSet()->GetItem
<SfxStringItem
>(DSID_CONNECTURL
);
454 const DbuTypeCollectionItem
* pTypeCollection
= m_pItemSetHelper
->getOutputSet()->GetItem
<DbuTypeCollectionItem
>(DSID_TYPECOLLECTION
);
456 OSL_ENSURE(pUrlItem
,"Connection URL is NULL. -> GPF!");
457 OSL_ENSURE(pTypeCollection
, "ODbDataSourceAdministrationHelper::getDatasourceType: invalid items in the source set!");
458 ::dbaccess::ODsnTypeCollection
* pCollection
= pTypeCollection
->getCollection();
459 OSL_ENSURE(pCollection
, "ODbDataSourceAdministrationHelper::getDatasourceType: invalid type collection!");
461 switch( pCollection
->determineType(eType
) )
463 case ::dbaccess::DST_DBASE
:
464 case ::dbaccess::DST_FLAT
:
465 case ::dbaccess::DST_CALC
:
466 case ::dbaccess::DST_WRITER
:
468 case ::dbaccess::DST_MSACCESS
:
469 case ::dbaccess::DST_MSACCESS_2007
:
471 OUString sFileName
= pCollection
->cutPrefix(pUrlItem
->GetValue());
472 OUString sNewFileName
;
473 if ( ::osl::FileBase::getSystemPathFromFileURL( sFileName
, sNewFileName
) == ::osl::FileBase::E_None
)
475 sNewUrl
+= sNewFileName
;
479 case ::dbaccess::DST_MYSQL_NATIVE
:
480 case ::dbaccess::DST_MYSQL_JDBC
:
482 const SfxStringItem
* pHostName
= m_pItemSetHelper
->getOutputSet()->GetItem
<SfxStringItem
>(DSID_CONN_HOSTNAME
);
483 const SfxInt32Item
* pPortNumber
= m_pItemSetHelper
->getOutputSet()->GetItem
<SfxInt32Item
>(DSID_MYSQL_PORTNUMBER
);
484 const SfxStringItem
* pDatabaseName
= m_pItemSetHelper
->getOutputSet()->GetItem
<SfxStringItem
>(DSID_DATABASENAME
);
485 sNewUrl
= lcl_createHostWithPort(pHostName
,pPortNumber
);
486 OUString sDatabaseName
= pDatabaseName
? pDatabaseName
->GetValue() : OUString();
487 if ( !sDatabaseName
.getLength() && pUrlItem
)
488 sDatabaseName
= pCollection
->cutPrefix( pUrlItem
->GetValue() );
489 // TODO: what's that? Why is the database name transported via the URL Item?
490 // Huh? Anybody there?
491 // OJ: It is needed when the connection properties are changed. There the URL is used for every type.
493 if ( !sDatabaseName
.isEmpty() )
495 sNewUrl
+= "/" + sDatabaseName
;
499 case ::dbaccess::DST_ORACLE_JDBC
:
501 const SfxStringItem
* pHostName
= m_pItemSetHelper
->getOutputSet()->GetItem
<SfxStringItem
>(DSID_CONN_HOSTNAME
);
502 const SfxInt32Item
* pPortNumber
= m_pItemSetHelper
->getOutputSet()->GetItem
<SfxInt32Item
>(DSID_ORACLE_PORTNUMBER
);
503 const SfxStringItem
* pDatabaseName
= m_pItemSetHelper
->getOutputSet()->GetItem
<SfxStringItem
>(DSID_DATABASENAME
);
504 if ( pHostName
&& pHostName
->GetValue().getLength() )
506 sNewUrl
= "@" + lcl_createHostWithPort(pHostName
,pPortNumber
);
507 OUString sDatabaseName
= pDatabaseName
? pDatabaseName
->GetValue() : OUString();
508 if ( sDatabaseName
.isEmpty() && pUrlItem
)
509 sDatabaseName
= pCollection
->cutPrefix( pUrlItem
->GetValue() );
510 if ( !sDatabaseName
.isEmpty() )
512 sNewUrl
+= ":" + sDatabaseName
;
516 { // here someone entered a JDBC url which looks like oracle, so we have to use the url property
521 case ::dbaccess::DST_LDAP
:
523 const SfxInt32Item
* pPortNumber
= m_pItemSetHelper
->getOutputSet()->GetItem
<SfxInt32Item
>(DSID_CONN_LDAP_PORTNUMBER
);
524 sNewUrl
= pCollection
->cutPrefix(pUrlItem
->GetValue()) + lcl_createHostWithPort(nullptr,pPortNumber
);
527 case ::dbaccess::DST_POSTGRES
:
529 sNewUrl
= pCollection
->cutPrefix(pUrlItem
->GetValue());
530 OUString
rURL(comphelper::string::stripEnd(pUrlItem
->GetValue(), '*'));
531 const SfxStringItem
* pHostName
= m_pItemSetHelper
->getOutputSet()->GetItem
<SfxStringItem
>(DSID_CONN_HOSTNAME
);
532 const SfxInt32Item
* pPortNumber
= m_pItemSetHelper
->getOutputSet()->GetItem
<SfxInt32Item
>(DSID_POSTGRES_PORTNUMBER
);
533 const SfxStringItem
* pDatabaseName
= m_pItemSetHelper
->getOutputSet()->GetItem
<SfxStringItem
>(DSID_DATABASENAME
);
534 if (pHostName
&& pHostName
->GetValue().getLength())
536 OUString
hostname( pHostName
->GetValue() );
537 hostname
= hostname
.replaceAll( "\\", "\\\\");
538 hostname
= hostname
.replaceAll( "\'", "\\'");
539 hostname
= "'" + hostname
+ "'";
540 rURL
+= " host=" + hostname
;
542 // tdf#157260: if port is already in the URL, don't add another one
543 if (pPortNumber
&& pPortNumber
->GetValue() && (rURL
.indexOf("port=") == -1))
545 OUString port
= "'" + OUString::number(pPortNumber
->GetValue()) + "'";
546 rURL
+= " port=" + port
;
548 if (pDatabaseName
&& pDatabaseName
->GetValue().getLength())
550 OUString
dbname( pDatabaseName
->GetValue() );
551 dbname
= dbname
.replaceAll( "\\", "\\\\");
552 dbname
= dbname
.replaceAll( "\'", "\\'");
553 dbname
= "'" + dbname
+ "'";
554 rURL
+= " dbname=" + dbname
;
560 case ::dbaccess::DST_JDBC
:
565 if ( !sNewUrl
.isEmpty() )
566 sNewUrl
= pCollection
->getPrefix(eType
) + sNewUrl
;
568 sNewUrl
= pUrlItem
->GetValue();
575 struct PropertyValueLess
577 bool operator() (const PropertyValue
& x
, const PropertyValue
& y
) const
578 { return x
.Name
< y
.Name
; } // construct prevents a MSVC6 warning
583 typedef std::set
<PropertyValue
, PropertyValueLess
> PropertyValueSet
;
585 void ODbDataSourceAdministrationHelper::translateProperties(const Reference
< XPropertySet
>& _rxSource
, SfxItemSet
& _rDest
)
589 for (auto const& elem
: m_aDirectPropTranslator
)
591 // get the property value
595 aValue
= _rxSource
->getPropertyValue(elem
.second
);
599 SAL_WARN("dbaccess", "ODbDataSourceAdministrationHelper::translateProperties: could not extract the property "
602 // transfer it into an item
603 implTranslateProperty(_rDest
, elem
.first
, aValue
);
606 // get the additional information
607 Sequence
< PropertyValue
> aAdditionalInfo
;
610 _rxSource
->getPropertyValue(PROPERTY_INFO
) >>= aAdditionalInfo
;
612 catch(Exception
&) { }
614 // collect the names of the additional settings
615 PropertyValueSet aInfos
;
616 for (const PropertyValue
& rAdditionalInfo
: std::as_const(aAdditionalInfo
))
618 if( rAdditionalInfo
.Name
== "JDBCDRV" )
620 PropertyValue
aCompatibility(rAdditionalInfo
);
621 aCompatibility
.Name
= "JavaDriverClass";
622 aInfos
.insert(aCompatibility
);
625 aInfos
.insert(rAdditionalInfo
);
628 // go through all known translations and check if we have such a setting
629 if ( !aInfos
.empty() )
631 PropertyValue aSearchFor
;
632 for (auto const& elem
: m_aIndirectPropTranslator
)
634 aSearchFor
.Name
= elem
.second
;
635 PropertyValueSet::const_iterator aInfoPos
= aInfos
.find(aSearchFor
);
636 if (aInfos
.end() != aInfoPos
)
637 // the property is contained in the info sequence
638 // -> transfer it into an item
639 implTranslateProperty(_rDest
, elem
.first
, aInfoPos
->Value
);
648 Reference
<XStorable
> xStore(getDataSourceOrModel(_rxSource
),UNO_QUERY
);
649 _rDest
.Put(SfxBoolItem(DSID_READONLY
, !xStore
.is() || xStore
->isReadonly() ));
653 TOOLS_WARN_EXCEPTION("dbaccess", "IsReadOnly throws");
657 void ODbDataSourceAdministrationHelper::translateProperties(const SfxItemSet
& _rSource
, const Reference
< XPropertySet
>& _rxDest
)
659 OSL_ENSURE(_rxDest
.is(), "ODbDataSourceAdministrationHelper::translateProperties: invalid property set!");
663 // the property set info
664 Reference
< XPropertySetInfo
> xInfo
;
665 try { xInfo
= _rxDest
->getPropertySetInfo(); }
666 catch(Exception
&) { }
668 static const OUStringLiteral
sUrlProp(u
"URL");
669 // transfer the direct properties
670 for (auto const& elem
: m_aDirectPropTranslator
)
672 const SfxPoolItem
* pCurrentItem
= _rSource
.GetItem(static_cast<sal_uInt16
>(elem
.first
));
675 sal_Int16 nAttributes
= PropertyAttribute::READONLY
;
678 try { nAttributes
= xInfo
->getPropertyByName(elem
.second
).Attributes
; }
679 catch(Exception
&) { }
681 if ((nAttributes
& PropertyAttribute::READONLY
) == 0)
683 if ( sUrlProp
== elem
.second
)
685 Any
aValue(getConnectionURL());
686 // aValue <<= OUString();
687 lcl_putProperty(_rxDest
, elem
.second
,aValue
);
690 implTranslateProperty(_rxDest
, elem
.second
, pCurrentItem
);
695 // now for the indirect properties
697 Sequence
< PropertyValue
> aInfo
;
698 // the original properties
701 _rxDest
->getPropertyValue(PROPERTY_INFO
) >>= aInfo
;
703 catch(Exception
&) { }
705 // overwrite and extend them
706 fillDatasourceInfo(_rSource
, aInfo
);
707 // and propagate the (newly composed) sequence to the set
708 lcl_putProperty(_rxDest
,PROPERTY_INFO
, Any(aInfo
));
711 void ODbDataSourceAdministrationHelper::fillDatasourceInfo(const SfxItemSet
& _rSource
, Sequence
< css::beans::PropertyValue
>& _rInfo
)
713 // within the current "Info" sequence, replace the ones we can examine from the item set
714 // (we don't just fill a completely new sequence with our own items, but we preserve any properties unknown to
717 // first determine which of all the items are relevant for the data source (depends on the connection url)
718 const OUString eType
= getDatasourceType(_rSource
);
719 const ::connectivity::DriversConfig
aDriverConfig(getORB());
720 const ::comphelper::NamedValueCollection
& aProperties
= aDriverConfig
.getProperties(eType
);
722 // collect the translated property values for the relevant items
723 PropertyValueSet aRelevantSettings
;
724 MapInt2String::const_iterator aTranslation
;
725 for (ItemID detailId
= DSID_FIRST_ITEM_ID
; detailId
<= DSID_LAST_ITEM_ID
; ++detailId
)
727 const SfxPoolItem
* pCurrent
= _rSource
.GetItem(static_cast<sal_uInt16
>(detailId
));
728 aTranslation
= m_aIndirectPropTranslator
.find(detailId
);
729 if ( pCurrent
&& (m_aIndirectPropTranslator
.end() != aTranslation
) &&
730 aProperties
.has(aTranslation
->second
) )
732 if ( aTranslation
->second
== INFO_CHARSET
)
735 implTranslateProperty(pCurrent
) >>= sCharSet
;
736 if ( !sCharSet
.isEmpty() )
737 aRelevantSettings
.insert(PropertyValue(aTranslation
->second
, 0, Any(sCharSet
), PropertyState_DIRECT_VALUE
));
740 aRelevantSettings
.insert(PropertyValue(aTranslation
->second
, 0, implTranslateProperty(pCurrent
), PropertyState_DIRECT_VALUE
));
744 // settings to preserve
745 MapInt2String aPreservedSettings
;
747 // now aRelevantSettings contains all the property values relevant for the current data source type,
748 // check the original sequence if it already contains any of these values (which have to be overwritten, then)
749 PropertyValue
* pInfo
= _rInfo
.getArray();
750 PropertyValue aSearchFor
;
751 sal_Int32 nObsoleteSetting
= -1;
752 sal_Int32 nCount
= _rInfo
.getLength();
753 for (sal_Int32 i
= 0; i
< nCount
; ++i
, ++pInfo
)
755 aSearchFor
.Name
= pInfo
->Name
;
756 PropertyValueSet::const_iterator aOverwrittenSetting
= aRelevantSettings
.find(aSearchFor
);
757 if (aRelevantSettings
.end() != aOverwrittenSetting
)
758 { // the setting was present in the original sequence, and it is to be overwritten -> replace it
759 if ( pInfo
->Value
!= aOverwrittenSetting
->Value
)
760 *pInfo
= *aOverwrittenSetting
;
761 aRelevantSettings
.erase(aOverwrittenSetting
);
763 else if( pInfo
->Name
== "JDBCDRV" )
764 { // this is a compatibility setting, remove it from the sequence (it's replaced by JavaDriverClass)
765 nObsoleteSetting
= i
;
768 aPreservedSettings
[i
] = pInfo
->Name
;
770 if (-1 != nObsoleteSetting
)
771 ::comphelper::removeElementAt(_rInfo
, nObsoleteSetting
);
773 if ( !aPreservedSettings
.empty() )
774 { // check if there are settings which
775 // * are known as indirect properties
776 // * but not relevant for the current data source type
777 // These settings have to be removed: If they're not relevant, we have no UI for changing them.
779 // for this, we need a string-controlled quick access to m_aIndirectPropTranslator
780 std::set
<OUString
> aIndirectProps
;
781 std::transform(m_aIndirectPropTranslator
.begin(),
782 m_aIndirectPropTranslator
.end(),
783 std::inserter(aIndirectProps
,aIndirectProps
.begin()),
784 ::o3tl::select2nd
< MapInt2String::value_type
>());
786 // now check the to-be-preserved props
787 std::vector
< sal_Int32
> aRemoveIndexes
;
788 sal_Int32 nPositionCorrector
= 0;
789 for (auto const& preservedSetting
: aPreservedSettings
)
791 if (aIndirectProps
.end() != aIndirectProps
.find(preservedSetting
.second
))
793 aRemoveIndexes
.push_back(preservedSetting
.first
- nPositionCorrector
);
794 ++nPositionCorrector
;
797 // now finally remove all such props
798 for (auto const& removeIndex
: aRemoveIndexes
)
799 ::comphelper::removeElementAt(_rInfo
, removeIndex
);
802 Sequence
< Any
> aTypeSettings
;
803 aTypeSettings
= aProperties
.getOrDefault("TypeInfoSettings",aTypeSettings
);
804 // here we have a special entry for types from oracle
805 if ( aTypeSettings
.hasElements() )
807 aRelevantSettings
.insert(PropertyValue("TypeInfoSettings", 0, Any(aTypeSettings
), PropertyState_DIRECT_VALUE
));
810 // check which values are still left ('cause they were not present in the original sequence, but are to be set)
811 if ( aRelevantSettings
.empty() )
814 sal_Int32 nOldLength
= _rInfo
.getLength();
815 _rInfo
.realloc(nOldLength
+ aRelevantSettings
.size());
816 PropertyValue
* pAppendValues
= _rInfo
.getArray() + nOldLength
;
817 for (auto const& relevantSetting
: aRelevantSettings
)
819 if ( relevantSetting
.Name
== INFO_CHARSET
)
822 relevantSetting
.Value
>>= sCharSet
;
823 if ( !sCharSet
.isEmpty() )
824 *pAppendValues
= relevantSetting
;
827 *pAppendValues
= relevantSetting
;
832 Any
ODbDataSourceAdministrationHelper::implTranslateProperty(const SfxPoolItem
* _pItem
)
834 // translate the SfxPoolItem
837 const SfxStringItem
* pStringItem
= dynamic_cast<const SfxStringItem
*>( _pItem
);
838 const SfxBoolItem
* pBoolItem
= dynamic_cast<const SfxBoolItem
*>( _pItem
);
839 const OptionalBoolItem
* pOptBoolItem
= dynamic_cast<const OptionalBoolItem
*>( _pItem
);
840 const SfxInt32Item
* pInt32Item
= dynamic_cast< const SfxInt32Item
* >( _pItem
);
841 const OStringListItem
* pStringListItem
= dynamic_cast<const OStringListItem
*>( _pItem
);
845 aValue
<<= pStringItem
->GetValue();
847 else if ( pBoolItem
)
849 aValue
<<= pBoolItem
->GetValue();
851 else if ( pOptBoolItem
)
853 if ( !pOptBoolItem
->HasValue() )
856 aValue
<<= pOptBoolItem
->GetValue();
858 else if ( pInt32Item
)
860 aValue
<<= pInt32Item
->GetValue();
862 else if ( pStringListItem
)
864 aValue
<<= pStringListItem
->getList();
868 OSL_FAIL("ODbDataSourceAdministrationHelper::implTranslateProperty: unsupported item type!");
875 void ODbDataSourceAdministrationHelper::implTranslateProperty(const Reference
< XPropertySet
>& _rxSet
, const OUString
& _rName
, const SfxPoolItem
* _pItem
)
877 Any aValue
= implTranslateProperty(_pItem
);
878 lcl_putProperty(_rxSet
, _rName
,aValue
);
881 OString
ODbDataSourceAdministrationHelper::translatePropertyId( sal_Int32 _nId
)
885 MapInt2String::const_iterator aPos
= m_aDirectPropTranslator
.find( _nId
);
886 if ( m_aDirectPropTranslator
.end() != aPos
)
888 aString
= aPos
->second
;
892 MapInt2String::const_iterator indirectPos
= m_aIndirectPropTranslator
.find( _nId
);
893 if ( m_aIndirectPropTranslator
.end() != indirectPos
)
894 aString
= indirectPos
->second
;
897 return OUStringToOString( aString
, RTL_TEXTENCODING_ASCII_US
);
899 template<class T
> static bool checkItemType(const SfxPoolItem
* pItem
){ return dynamic_cast<const T
*>(pItem
) != nullptr;}
901 void ODbDataSourceAdministrationHelper::implTranslateProperty( SfxItemSet
& _rSet
, sal_Int32 _nId
, const Any
& _rValue
)
903 switch ( _rValue
.getValueType().getTypeClass() )
905 case TypeClass_STRING
:
906 if ( implCheckItemType( _rSet
, _nId
, checkItemType
<SfxStringItem
> ) )
910 _rSet
.Put(SfxStringItem(_nId
, sValue
));
913 SAL_WARN( "dbaccess", "ODbDataSourceAdministrationHelper::implTranslateProperty: invalid property value ("
914 << translatePropertyId(_nId
) << " should be no string)!");
918 case TypeClass_BOOLEAN
:
919 if ( implCheckItemType( _rSet
, _nId
, checkItemType
<SfxBoolItem
> ) )
923 _rSet
.Put(SfxBoolItem(_nId
, bVal
));
925 else if ( implCheckItemType( _rSet
, _nId
, checkItemType
<OptionalBoolItem
> ) )
927 OptionalBoolItem
aItem( _nId
);
928 if ( _rValue
.hasValue() )
932 aItem
.SetValue( bValue
);
939 SAL_WARN( "dbaccess", "ODbDataSourceAdministrationHelper::implTranslateProperty: invalid property value ("
940 << translatePropertyId(_nId
)
941 << " should be no boolean)!");
946 if ( implCheckItemType( _rSet
, _nId
, checkItemType
<SfxInt32Item
> ) )
948 sal_Int32 nValue
= 0;
950 _rSet
.Put( SfxInt32Item( TypedWhichId
<SfxInt32Item
>(_nId
), nValue
) );
953 SAL_WARN( "dbaccess", "ODbDataSourceAdministrationHelper::implTranslateProperty: invalid property value ("
954 << translatePropertyId(_nId
)
955 << " should be no int)!");
959 case TypeClass_SEQUENCE
:
960 if ( implCheckItemType( _rSet
, _nId
, checkItemType
<OStringListItem
> ) )
962 // determine the element type
963 TypeDescription
aTD(_rValue
.getValueType());
964 typelib_IndirectTypeDescription
* pSequenceTD
=
965 reinterpret_cast< typelib_IndirectTypeDescription
* >(aTD
.get());
966 OSL_ENSURE(pSequenceTD
&& pSequenceTD
->pType
, "ODbDataSourceAdministrationHelper::implTranslateProperty: invalid sequence type!");
968 Type
aElementType(pSequenceTD
->pType
);
969 switch (aElementType
.getTypeClass())
971 case TypeClass_STRING
:
973 Sequence
< OUString
> aStringList
;
974 _rValue
>>= aStringList
;
975 _rSet
.Put(OStringListItem(_nId
, aStringList
));
979 OSL_FAIL("ODbDataSourceAdministrationHelper::implTranslateProperty: unsupported property value type!");
983 SAL_WARN( "dbaccess", "ODbDataSourceAdministrationHelper::implTranslateProperty: invalid property value ("
984 << translatePropertyId(_nId
)
985 << " should be no string sequence)!");
990 _rSet
.ClearItem(_nId
);
994 OSL_FAIL("ODbDataSourceAdministrationHelper::implTranslateProperty: unsupported property value type!");
998 OUString
ODbDataSourceAdministrationHelper::getDocumentUrl(SfxItemSet
const & _rDest
)
1000 const SfxStringItem
* pUrlItem
= _rDest
.GetItem
<SfxStringItem
>(DSID_DOCUMENT_URL
);
1001 OSL_ENSURE(pUrlItem
,"Document URL is NULL. -> GPF!");
1002 return pUrlItem
->GetValue();
1005 void ODbDataSourceAdministrationHelper::convertUrl(SfxItemSet
& _rDest
)
1007 OUString eType
= getDatasourceType(_rDest
);
1009 const SfxStringItem
* pUrlItem
= _rDest
.GetItem
<SfxStringItem
>(DSID_CONNECTURL
);
1010 const DbuTypeCollectionItem
* pTypeCollection
= _rDest
.GetItem
<DbuTypeCollectionItem
>(DSID_TYPECOLLECTION
);
1012 OSL_ENSURE(pUrlItem
,"Connection URL is NULL. -> GPF!");
1013 OSL_ENSURE(pTypeCollection
, "ODbAdminDialog::getDatasourceType: invalid items in the source set!");
1014 ::dbaccess::ODsnTypeCollection
* pCollection
= pTypeCollection
->getCollection();
1015 OSL_ENSURE(pCollection
, "ODbAdminDialog::getDatasourceType: invalid type collection!");
1017 TypedWhichId
<SfxInt32Item
> nPortNumberId(0);
1018 sal_Int32 nPortNumber
= -1;
1019 OUString sNewHostName
;
1022 pCollection
->extractHostNamePort(pUrlItem
->GetValue(),sUrlPart
,sNewHostName
,nPortNumber
);
1023 const ::dbaccess::DATASOURCE_TYPE eTy
= pCollection
->determineType(eType
);
1027 case ::dbaccess::DST_MYSQL_NATIVE
:
1028 case ::dbaccess::DST_MYSQL_JDBC
:
1029 nPortNumberId
= DSID_MYSQL_PORTNUMBER
;
1031 case ::dbaccess::DST_ORACLE_JDBC
:
1032 nPortNumberId
= DSID_ORACLE_PORTNUMBER
;
1034 case ::dbaccess::DST_LDAP
:
1035 nPortNumberId
= DSID_CONN_LDAP_PORTNUMBER
;
1037 case ::dbaccess::DST_POSTGRES
:
1038 nPortNumberId
= DSID_POSTGRES_PORTNUMBER
;
1044 if ( !sUrlPart
.isEmpty() )
1046 if ( eTy
== ::dbaccess::DST_MYSQL_NATIVE
)
1048 _rDest
.Put( SfxStringItem( DSID_DATABASENAME
, sUrlPart
) );
1052 OUString sNewUrl
= pCollection
->getPrefix(eType
) + sUrlPart
;
1053 _rDest
.Put( SfxStringItem( DSID_CONNECTURL
, sNewUrl
) );
1057 if ( !sNewHostName
.isEmpty() )
1058 _rDest
.Put(SfxStringItem(DSID_CONN_HOSTNAME
, sNewHostName
));
1060 if ( nPortNumber
!= -1 && nPortNumberId
!= TypedWhichId
<SfxInt32Item
>(0) )
1061 _rDest
.Put(SfxInt32Item(nPortNumberId
, nPortNumber
));
1065 bool ODbDataSourceAdministrationHelper::saveChanges(const SfxItemSet
& _rSource
)
1067 // put the remembered settings into the property set
1068 Reference
<XPropertySet
> xDatasource
= getCurrentDataSource();
1069 if ( !xDatasource
.is() )
1072 translateProperties(_rSource
,xDatasource
);
1077 void ODbDataSourceAdministrationHelper::setDataSourceOrName( const Any
& _rDataSourceOrName
)
1079 OSL_ENSURE( !m_aDataSourceOrName
.hasValue(), "ODbDataSourceAdministrationHelper::setDataSourceOrName: already have one!" );
1080 // hmm. We could reset m_xDatasource/m_xModel, probably, and continue working
1081 m_aDataSourceOrName
= _rDataSourceOrName
;
1084 // DbuTypeCollectionItem
1085 DbuTypeCollectionItem::DbuTypeCollectionItem(sal_Int16 _nWhich
, ::dbaccess::ODsnTypeCollection
* _pCollection
)
1086 :SfxPoolItem(_nWhich
)
1087 ,m_pCollection(_pCollection
)
1091 DbuTypeCollectionItem::DbuTypeCollectionItem(const DbuTypeCollectionItem
& _rSource
)
1092 :SfxPoolItem(_rSource
)
1093 ,m_pCollection(_rSource
.getCollection())
1097 bool DbuTypeCollectionItem::operator==(const SfxPoolItem
& _rItem
) const
1099 return SfxPoolItem::operator==(_rItem
) &&
1100 static_cast<const DbuTypeCollectionItem
&>( _rItem
).getCollection() == getCollection();
1103 DbuTypeCollectionItem
* DbuTypeCollectionItem::Clone(SfxItemPool
* /*_pPool*/) const
1105 return new DbuTypeCollectionItem(*this);
1108 } // namespace dbaui
1110 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */