Line data Source code
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 "ZPoolCollection.hxx"
21 : #include "ZDriverWrapper.hxx"
22 : #include "ZConnectionPool.hxx"
23 : #include <com/sun/star/configuration/theDefaultProvider.hpp>
24 : #include <com/sun/star/container/XHierarchicalNameAccess.hpp>
25 : #include <com/sun/star/beans/NamedValue.hpp>
26 : #include <com/sun/star/beans/PropertyValue.hpp>
27 : #include <com/sun/star/frame/Desktop.hpp>
28 : #include <com/sun/star/reflection/ProxyFactory.hpp>
29 : #include <com/sun/star/sdbc/DriverManager.hpp>
30 : #include <comphelper/extract.hxx>
31 : #include <comphelper/processfactory.hxx>
32 : #include <com/sun/star/beans/XPropertySet.hpp>
33 : #include "diagnose_ex.h"
34 :
35 : using namespace ::com::sun::star::uno;
36 : using namespace ::com::sun::star::lang;
37 : using namespace ::com::sun::star::sdbc;
38 : using namespace ::com::sun::star::beans;
39 : using namespace ::com::sun::star::container;
40 : using namespace ::com::sun::star::reflection;
41 : using namespace ::osl;
42 : using namespace connectivity;
43 :
44 : //--------------------------------------------------------------------
45 0 : static const ::rtl::OUString& getConnectionPoolNodeName()
46 : {
47 0 : static ::rtl::OUString s_sNodeName( "org.openoffice.Office.DataAccess/ConnectionPool" );
48 0 : return s_sNodeName;
49 : }
50 : //--------------------------------------------------------------------
51 0 : static const ::rtl::OUString& getEnablePoolingNodeName()
52 : {
53 0 : static ::rtl::OUString s_sNodeName( "EnablePooling" );
54 0 : return s_sNodeName;
55 : }
56 : //--------------------------------------------------------------------
57 0 : static const ::rtl::OUString& getDriverNameNodeName()
58 : {
59 0 : static ::rtl::OUString s_sNodeName( "DriverName" );
60 0 : return s_sNodeName;
61 : }
62 : // -----------------------------------------------------------------------------
63 0 : static const ::rtl::OUString& getDriverSettingsNodeName()
64 : {
65 0 : static ::rtl::OUString s_sNodeName( "DriverSettings" );
66 0 : return s_sNodeName;
67 : }
68 : //--------------------------------------------------------------------------
69 0 : static const ::rtl::OUString& getEnableNodeName()
70 : {
71 0 : static ::rtl::OUString s_sNodeName( "Enable" );
72 0 : return s_sNodeName;
73 : }
74 :
75 : //--------------------------------------------------------------------
76 0 : OPoolCollection::OPoolCollection(const Reference< XComponentContext >& _rxContext)
77 0 : :m_xContext(_rxContext)
78 : {
79 : // bootstrap all objects supporting the .sdb.Driver service
80 0 : m_xManager = DriverManager::create( m_xContext );
81 :
82 0 : m_xProxyFactory = ProxyFactory::create( m_xContext );
83 :
84 0 : Reference<XPropertySet> xProp(getConfigPoolRoot(),UNO_QUERY);
85 0 : if ( xProp.is() )
86 0 : xProp->addPropertyChangeListener(getEnablePoolingNodeName(),this);
87 : // attach as desktop listener to know when we have to release our pools
88 0 : osl_atomic_increment( &m_refCount );
89 : {
90 :
91 0 : m_xDesktop = ::com::sun::star::frame::Desktop::create( m_xContext );
92 0 : m_xDesktop->addTerminateListener(this);
93 :
94 : }
95 0 : osl_atomic_decrement( &m_refCount );
96 0 : }
97 : // -----------------------------------------------------------------------------
98 0 : OPoolCollection::~OPoolCollection()
99 : {
100 0 : clearConnectionPools(sal_False);
101 0 : }
102 : // -----------------------------------------------------------------------------
103 0 : Reference< XConnection > SAL_CALL OPoolCollection::getConnection( const ::rtl::OUString& _rURL ) throw(SQLException, RuntimeException)
104 : {
105 0 : return getConnectionWithInfo(_rURL,Sequence< PropertyValue >());
106 : }
107 : // -----------------------------------------------------------------------------
108 0 : Reference< XConnection > SAL_CALL OPoolCollection::getConnectionWithInfo( const ::rtl::OUString& _rURL, const Sequence< PropertyValue >& _rInfo ) throw(SQLException, RuntimeException)
109 : {
110 0 : MutexGuard aGuard(m_aMutex);
111 0 : Reference< XConnection > xConnection;
112 0 : Reference< XDriver > xDriver;
113 0 : Reference< XInterface > xDriverNode;
114 0 : ::rtl::OUString sImplName;
115 0 : if(isPoolingEnabledByUrl(_rURL,xDriver,sImplName,xDriverNode) && xDriver.is())
116 : {
117 0 : OConnectionPool* pConnectionPool = getConnectionPool(sImplName,xDriver,xDriverNode);
118 :
119 0 : if(pConnectionPool)
120 0 : xConnection = pConnectionPool->getConnectionWithInfo(_rURL,_rInfo);
121 : }
122 0 : else if(xDriver.is())
123 0 : xConnection = xDriver->connect(_rURL,_rInfo);
124 :
125 0 : return xConnection;
126 : }
127 : // -----------------------------------------------------------------------------
128 0 : void SAL_CALL OPoolCollection::setLoginTimeout( sal_Int32 seconds ) throw(RuntimeException)
129 : {
130 0 : MutexGuard aGuard(m_aMutex);
131 0 : m_xManager->setLoginTimeout(seconds);
132 0 : }
133 : // -----------------------------------------------------------------------------
134 0 : sal_Int32 SAL_CALL OPoolCollection::getLoginTimeout( ) throw(RuntimeException)
135 : {
136 0 : MutexGuard aGuard(m_aMutex);
137 0 : return m_xManager->getLoginTimeout();
138 : }
139 : // -----------------------------------------------------------------------------
140 0 : ::rtl::OUString SAL_CALL OPoolCollection::getImplementationName( ) throw(RuntimeException)
141 : {
142 0 : MutexGuard aGuard(m_aMutex);
143 0 : return getImplementationName_Static();
144 : }
145 :
146 : //--------------------------------------------------------------------------
147 0 : sal_Bool SAL_CALL OPoolCollection::supportsService( const ::rtl::OUString& _rServiceName ) throw(RuntimeException)
148 : {
149 0 : Sequence< ::rtl::OUString > aSupported(getSupportedServiceNames());
150 0 : const ::rtl::OUString* pSupported = aSupported.getConstArray();
151 0 : const ::rtl::OUString* pEnd = pSupported + aSupported.getLength();
152 0 : for (;pSupported != pEnd && !pSupported->equals(_rServiceName); ++pSupported)
153 : ;
154 :
155 0 : return pSupported != pEnd;
156 : }
157 :
158 : //--------------------------------------------------------------------------
159 0 : Sequence< ::rtl::OUString > SAL_CALL OPoolCollection::getSupportedServiceNames( ) throw(RuntimeException)
160 : {
161 0 : return getSupportedServiceNames_Static();
162 : }
163 :
164 : //---------------------------------------OPoolCollection----------------------------------
165 0 : Reference< XInterface > SAL_CALL OPoolCollection::CreateInstance(const Reference< XMultiServiceFactory >& _rxFactory)
166 : {
167 0 : return static_cast<XDriverManager*>(new OPoolCollection(comphelper::getComponentContext(_rxFactory)));
168 : }
169 :
170 : //--------------------------------------------------------------------------
171 0 : ::rtl::OUString SAL_CALL OPoolCollection::getImplementationName_Static( ) throw(RuntimeException)
172 : {
173 0 : return ::rtl::OUString("com.sun.star.sdbc.OConnectionPool");
174 : }
175 :
176 : //--------------------------------------------------------------------------
177 0 : Sequence< ::rtl::OUString > SAL_CALL OPoolCollection::getSupportedServiceNames_Static( ) throw(RuntimeException)
178 : {
179 0 : Sequence< ::rtl::OUString > aSupported(1);
180 0 : aSupported[0] = ::rtl::OUString("com.sun.star.sdbc.ConnectionPool");
181 0 : return aSupported;
182 : }
183 : // -----------------------------------------------------------------------------
184 0 : Reference< XDriver > SAL_CALL OPoolCollection::getDriverByURL( const ::rtl::OUString& _rURL ) throw(RuntimeException)
185 : {
186 : // returns the original driver when no connection pooling is enabled else it returns the proxy
187 0 : MutexGuard aGuard(m_aMutex);
188 :
189 0 : Reference< XDriver > xDriver;
190 0 : Reference< XInterface > xDriverNode;
191 0 : ::rtl::OUString sImplName;
192 0 : if(isPoolingEnabledByUrl(_rURL,xDriver,sImplName,xDriverNode))
193 : {
194 0 : Reference< XDriver > xExistentProxy;
195 : // look if we already have a proxy for this driver
196 0 : for ( ConstMapDriver2DriverRefIterator aLookup = m_aDriverProxies.begin();
197 0 : aLookup != m_aDriverProxies.end();
198 : ++aLookup
199 : )
200 : {
201 : // hold the proxy alive as long as we're in this loop round
202 0 : xExistentProxy = aLookup->second;
203 :
204 0 : if (xExistentProxy.is() && (aLookup->first.get() == xDriver.get()))
205 : // already created a proxy for this
206 0 : break;
207 : }
208 0 : if (xExistentProxy.is())
209 : {
210 0 : xDriver = xExistentProxy;
211 : }
212 : else
213 : { // create a new proxy for the driver
214 : // this allows us to control the connections created by it
215 0 : Reference< XAggregation > xDriverProxy = m_xProxyFactory->createProxy(xDriver.get());
216 : OSL_ENSURE(xDriverProxy.is(), "OConnectionPool::getDriverByURL: invalid proxy returned by the proxy factory!");
217 :
218 0 : OConnectionPool* pConnectionPool = getConnectionPool(sImplName,xDriver,xDriverNode);
219 0 : xDriver = new ODriverWrapper(xDriverProxy, pConnectionPool);
220 0 : }
221 : }
222 :
223 0 : return xDriver;
224 : }
225 : // -----------------------------------------------------------------------------
226 0 : sal_Bool OPoolCollection::isDriverPoolingEnabled(const ::rtl::OUString& _sDriverImplName,
227 : Reference< XInterface >& _rxDriverNode)
228 : {
229 0 : sal_Bool bEnabled = sal_False;
230 0 : Reference<XInterface> xConnectionPoolRoot = getConfigPoolRoot();
231 : // then look for which of them settings are stored in the configuration
232 0 : Reference< XNameAccess > xDirectAccess(openNode(getDriverSettingsNodeName(),xConnectionPoolRoot),UNO_QUERY);
233 :
234 0 : if(xDirectAccess.is())
235 : {
236 0 : Sequence< ::rtl::OUString > aDriverKeys = xDirectAccess->getElementNames();
237 0 : const ::rtl::OUString* pDriverKeys = aDriverKeys.getConstArray();
238 0 : const ::rtl::OUString* pDriverKeysEnd = pDriverKeys + aDriverKeys.getLength();
239 0 : for (;pDriverKeys != pDriverKeysEnd; ++pDriverKeys)
240 : {
241 : // the name of the driver in this round
242 0 : if(_sDriverImplName == *pDriverKeys)
243 : {
244 0 : _rxDriverNode = openNode(*pDriverKeys,xDirectAccess);
245 0 : if(_rxDriverNode.is())
246 0 : getNodeValue(getEnableNodeName(),_rxDriverNode) >>= bEnabled;
247 0 : break;
248 : }
249 0 : }
250 : }
251 0 : return bEnabled;
252 : }
253 : // -----------------------------------------------------------------------------
254 0 : sal_Bool OPoolCollection::isPoolingEnabled()
255 : {
256 : // the config node where all pooling relevant info are stored under
257 0 : Reference<XInterface> xConnectionPoolRoot = getConfigPoolRoot();
258 :
259 : // the global "enabled" flag
260 0 : sal_Bool bEnabled = sal_False;
261 0 : if(xConnectionPoolRoot.is())
262 0 : getNodeValue(getEnablePoolingNodeName(),xConnectionPoolRoot) >>= bEnabled;
263 0 : return bEnabled;
264 : }
265 : // -----------------------------------------------------------------------------
266 0 : Reference<XInterface> OPoolCollection::getConfigPoolRoot()
267 : {
268 0 : if(!m_xConfigNode.is())
269 0 : m_xConfigNode = createWithServiceFactory(getConnectionPoolNodeName());
270 0 : return m_xConfigNode;
271 : }
272 : // -----------------------------------------------------------------------------
273 0 : sal_Bool OPoolCollection::isPoolingEnabledByUrl(const ::rtl::OUString& _sUrl,
274 : Reference< XDriver >& _rxDriver,
275 : ::rtl::OUString& _rsImplName,
276 : Reference< XInterface >& _rxDriverNode)
277 : {
278 0 : sal_Bool bEnabled = sal_False;
279 0 : _rxDriver = m_xManager->getDriverByURL(_sUrl);
280 0 : if (_rxDriver.is() && isPoolingEnabled())
281 : {
282 0 : Reference< XServiceInfo > xSerivceInfo(_rxDriver,UNO_QUERY);
283 : OSL_ENSURE(xSerivceInfo.is(),"Each driver should have a XServiceInfo interface!");
284 :
285 0 : if(xSerivceInfo.is())
286 : {
287 : // look for the implementation name of the driver
288 0 : _rsImplName = xSerivceInfo->getImplementationName();
289 0 : bEnabled = isDriverPoolingEnabled(_rsImplName,_rxDriverNode);
290 0 : }
291 : }
292 0 : return bEnabled;
293 : }
294 : // -----------------------------------------------------------------------------
295 0 : void OPoolCollection::clearConnectionPools(sal_Bool _bDispose)
296 : {
297 0 : OConnectionPools::const_iterator aIter = m_aPools.begin();
298 0 : while(aIter != m_aPools.end())
299 : {
300 0 : aIter->second->clear(_bDispose);
301 0 : aIter->second->release();
302 0 : ::rtl::OUString sKeyValue = aIter->first;
303 0 : ++aIter;
304 0 : m_aPools.erase(sKeyValue);
305 0 : }
306 0 : }
307 : // -----------------------------------------------------------------------------
308 0 : OConnectionPool* OPoolCollection::getConnectionPool(const ::rtl::OUString& _sImplName,
309 : const Reference< XDriver >& _xDriver,
310 : const Reference< XInterface >& _xDriverNode)
311 : {
312 0 : OConnectionPool *pRet = 0;
313 0 : OConnectionPools::const_iterator aFind = m_aPools.find(_sImplName);
314 0 : if (aFind != m_aPools.end())
315 0 : pRet = aFind->second;
316 0 : else if (_xDriver.is() && _xDriverNode.is())
317 : {
318 0 : Reference<XPropertySet> xProp(_xDriverNode,UNO_QUERY);
319 0 : if(xProp.is())
320 0 : xProp->addPropertyChangeListener(getEnableNodeName(),this);
321 0 : OConnectionPool* pConnectionPool = new OConnectionPool(_xDriver,_xDriverNode,m_xProxyFactory);
322 0 : pConnectionPool->acquire();
323 0 : aFind = m_aPools.insert(OConnectionPools::value_type(_sImplName,pConnectionPool)).first;
324 0 : pRet = aFind->second;
325 : }
326 :
327 : OSL_ENSURE(pRet, "Could not query DriverManager from ConnectionPool!");
328 :
329 0 : return pRet;
330 : }
331 : // -----------------------------------------------------------------------------
332 0 : Reference< XInterface > OPoolCollection::createWithServiceFactory(const ::rtl::OUString& _rPath) const
333 : {
334 : return createWithProvider(
335 : com::sun::star::configuration::theDefaultProvider::get(m_xContext),
336 0 : _rPath);
337 : }
338 : //------------------------------------------------------------------------
339 0 : Reference< XInterface > OPoolCollection::createWithProvider(const Reference< XMultiServiceFactory >& _rxConfProvider,
340 : const ::rtl::OUString& _rPath) const
341 : {
342 : OSL_ASSERT(_rxConfProvider.is());
343 0 : Sequence< Any > args(1);
344 0 : args[0] = makeAny(
345 : NamedValue(
346 : rtl::OUString("nodepath"),
347 0 : makeAny(_rPath)));
348 : Reference< XInterface > xInterface(
349 0 : _rxConfProvider->createInstanceWithArguments(
350 : rtl::OUString( "com.sun.star.configuration.ConfigurationAccess"),
351 0 : args));
352 : OSL_ENSURE(
353 : xInterface.is(),
354 : "::createWithProvider: could not create the node access!");
355 0 : return xInterface;
356 : }
357 : // -----------------------------------------------------------------------------
358 0 : Reference<XInterface> OPoolCollection::openNode(const ::rtl::OUString& _rPath,const Reference<XInterface>& _xTreeNode) const throw()
359 : {
360 0 : Reference< XHierarchicalNameAccess > xHierarchyAccess(_xTreeNode, UNO_QUERY);
361 0 : Reference< XNameAccess > xDirectAccess(_xTreeNode, UNO_QUERY);
362 0 : Reference< XInterface > xNode;
363 :
364 : try
365 : {
366 0 : if (xDirectAccess.is() && xDirectAccess->hasByName(_rPath))
367 : {
368 0 : if (!::cppu::extractInterface(xNode, xDirectAccess->getByName(_rPath)))
369 : OSL_FAIL("OConfigurationNode::openNode: could not open the node!");
370 : }
371 0 : else if (xHierarchyAccess.is())
372 : {
373 0 : if (!::cppu::extractInterface(xNode, xHierarchyAccess->getByHierarchicalName(_rPath)))
374 : OSL_FAIL("OConfigurationNode::openNode: could not open the node!");
375 : }
376 :
377 : }
378 0 : catch(const NoSuchElementException&)
379 : {
380 : OSL_FAIL((::rtl::OString("::openNode: there is no element named ")
381 : += ::rtl::OString(_rPath.getStr(), _rPath.getLength(), RTL_TEXTENCODING_ASCII_US)
382 : += ::rtl::OString("!")).getStr());
383 : }
384 0 : catch(Exception&)
385 : {
386 : OSL_FAIL("OConfigurationNode::openNode: caught an exception while retrieving the node!");
387 : }
388 0 : return xNode;
389 : }
390 : // -----------------------------------------------------------------------------
391 0 : Any OPoolCollection::getNodeValue(const ::rtl::OUString& _rPath,const Reference<XInterface>& _xTreeNode) throw()
392 : {
393 0 : Reference< XHierarchicalNameAccess > xHierarchyAccess(_xTreeNode, UNO_QUERY);
394 0 : Reference< XNameAccess > xDirectAccess(_xTreeNode, UNO_QUERY);
395 0 : Any aReturn;
396 : try
397 : {
398 0 : if (xDirectAccess.is() && xDirectAccess->hasByName(_rPath) )
399 : {
400 0 : aReturn = xDirectAccess->getByName(_rPath);
401 : }
402 0 : else if (xHierarchyAccess.is())
403 : {
404 0 : aReturn = xHierarchyAccess->getByHierarchicalName(_rPath);
405 : }
406 : }
407 0 : catch(NoSuchElementException& e)
408 : {
409 : OSL_UNUSED( e ); // make compiler happy
410 : OSL_FAIL((::rtl::OString("::getNodeValue: caught a NoSuchElementException while trying to open ")
411 : += ::rtl::OString(e.Message.getStr(), e.Message.getLength(), RTL_TEXTENCODING_ASCII_US)
412 : += ::rtl::OString("!")).getStr());
413 : }
414 0 : return aReturn;
415 : }
416 : // -----------------------------------------------------------------------------
417 0 : void SAL_CALL OPoolCollection::queryTermination( const EventObject& /*Event*/ ) throw (::com::sun::star::frame::TerminationVetoException, RuntimeException)
418 : {
419 0 : }
420 : // -----------------------------------------------------------------------------
421 0 : void SAL_CALL OPoolCollection::notifyTermination( const EventObject& /*Event*/ ) throw (RuntimeException)
422 : {
423 0 : clearDesktop();
424 0 : }
425 : // -----------------------------------------------------------------------------
426 0 : void SAL_CALL OPoolCollection::disposing( const EventObject& Source ) throw (RuntimeException)
427 : {
428 0 : MutexGuard aGuard(m_aMutex);
429 0 : if ( m_xDesktop == Source.Source )
430 : {
431 0 : clearDesktop();
432 : }
433 : else
434 : {
435 : try
436 : {
437 0 : Reference<XPropertySet> xProp(Source.Source,UNO_QUERY);
438 0 : if(Source.Source == m_xConfigNode)
439 : {
440 0 : if ( xProp.is() )
441 0 : xProp->removePropertyChangeListener(getEnablePoolingNodeName(),this);
442 0 : m_xConfigNode.clear();
443 : }
444 0 : else if ( xProp.is() )
445 0 : xProp->removePropertyChangeListener(getEnableNodeName(),this);
446 : }
447 0 : catch(const Exception&)
448 : {
449 : OSL_FAIL("Exception caught");
450 : }
451 0 : }
452 0 : }
453 : // -----------------------------------------------------------------------------
454 0 : void SAL_CALL OPoolCollection::propertyChange( const ::com::sun::star::beans::PropertyChangeEvent& evt ) throw (RuntimeException)
455 : {
456 0 : MutexGuard aGuard(m_aMutex);
457 0 : if(evt.Source == m_xConfigNode)
458 : {
459 0 : sal_Bool bEnabled = sal_True;
460 0 : evt.NewValue >>= bEnabled;
461 0 : if(!bEnabled )
462 : {
463 0 : m_aDriverProxies.clear();
464 0 : m_aDriverProxies = MapDriver2DriverRef();
465 0 : OConnectionPools::iterator aIter = m_aPools.begin();
466 0 : for(;aIter != m_aPools.end();++aIter)
467 : {
468 0 : aIter->second->clear(sal_False);
469 0 : aIter->second->release();
470 : }
471 0 : m_aPools.clear();
472 0 : m_aPools = OConnectionPools();
473 : }
474 : }
475 0 : else if(evt.Source.is())
476 : {
477 0 : sal_Bool bEnabled = sal_True;
478 0 : evt.NewValue >>= bEnabled;
479 0 : if(!bEnabled)
480 : {
481 0 : ::rtl::OUString sThisDriverName;
482 0 : getNodeValue(getDriverNameNodeName(),evt.Source) >>= sThisDriverName;
483 : // 1nd relase the driver
484 : // look if we already have a proxy for this driver
485 0 : MapDriver2DriverRefIterator aLookup = m_aDriverProxies.begin();
486 0 : while( aLookup != m_aDriverProxies.end())
487 : {
488 0 : MapDriver2DriverRefIterator aFind = aLookup;
489 0 : Reference<XServiceInfo> xInfo(aLookup->first,UNO_QUERY);
490 0 : ++aLookup;
491 0 : if(xInfo.is() && xInfo->getImplementationName() == sThisDriverName)
492 0 : m_aDriverProxies.erase(aFind);
493 0 : }
494 :
495 : // 2nd clear the connectionpool
496 0 : OConnectionPools::iterator aFind = m_aPools.find(sThisDriverName);
497 0 : if(aFind != m_aPools.end() && aFind->second)
498 : {
499 0 : aFind->second->clear(sal_False);
500 0 : aFind->second->release();
501 0 : m_aPools.erase(aFind);
502 0 : }
503 : }
504 0 : }
505 0 : }
506 : // -----------------------------------------------------------------------------
507 0 : void OPoolCollection::clearDesktop()
508 : {
509 0 : clearConnectionPools(sal_True);
510 0 : if ( m_xDesktop.is() )
511 0 : m_xDesktop->removeTerminateListener(this);
512 0 : m_xDesktop.clear();
513 0 : }
514 : // -----------------------------------------------------------------------------
515 :
516 :
517 : /* vim:set shiftwidth=4 softtabstop=4 expandtab: */
|