19b5730f6SAndrew Rist /**************************************************************
2*1636bfc2Smseidel  *
39b5730f6SAndrew Rist  * Licensed to the Apache Software Foundation (ASF) under one
49b5730f6SAndrew Rist  * or more contributor license agreements.  See the NOTICE file
59b5730f6SAndrew Rist  * distributed with this work for additional information
69b5730f6SAndrew Rist  * regarding copyright ownership.  The ASF licenses this file
79b5730f6SAndrew Rist  * to you under the Apache License, Version 2.0 (the
89b5730f6SAndrew Rist  * "License"); you may not use this file except in compliance
99b5730f6SAndrew Rist  * with the License.  You may obtain a copy of the License at
10*1636bfc2Smseidel  *
119b5730f6SAndrew Rist  *   http://www.apache.org/licenses/LICENSE-2.0
12*1636bfc2Smseidel  *
139b5730f6SAndrew Rist  * Unless required by applicable law or agreed to in writing,
149b5730f6SAndrew Rist  * software distributed under the License is distributed on an
159b5730f6SAndrew Rist  * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
169b5730f6SAndrew Rist  * KIND, either express or implied.  See the License for the
179b5730f6SAndrew Rist  * specific language governing permissions and limitations
189b5730f6SAndrew Rist  * under the License.
19*1636bfc2Smseidel  *
209b5730f6SAndrew Rist  *************************************************************/
219b5730f6SAndrew Rist 
229b5730f6SAndrew Rist 
23cdf0e10cSrcweir 
24cdf0e10cSrcweir // MARKER(update_precomp.py): autogen include statement, do not remove
25cdf0e10cSrcweir #include "precompiled_connectivity.hxx"
26cdf0e10cSrcweir 
27cdf0e10cSrcweir #include <stdio.h>
28cdf0e10cSrcweir #include "ZConnectionPool.hxx"
29cdf0e10cSrcweir #include <com/sun/star/lang/XSingleServiceFactory.hpp>
30cdf0e10cSrcweir #include <com/sun/star/container/ElementExistException.hpp>
31cdf0e10cSrcweir #include <comphelper/extract.hxx>
32cdf0e10cSrcweir #include <comphelper/types.hxx>
33cdf0e10cSrcweir #include <com/sun/star/lang/XComponent.hpp>
34cdf0e10cSrcweir #include "ZPooledConnection.hxx"
35cdf0e10cSrcweir #include "ZPoolCollection.hxx"
36cdf0e10cSrcweir #ifndef _CONNECTIVITY_CONNECTIONWRAPPER_HXX_
37cdf0e10cSrcweir #include "connectivity/ConnectionWrapper.hxx"
38cdf0e10cSrcweir #endif
39cdf0e10cSrcweir #include <com/sun/star/beans/XPropertySet.hpp>
40cdf0e10cSrcweir #ifndef _CONNECTIVITY_CONNECTIONWRAPPER_HXX_
41cdf0e10cSrcweir #include "connectivity/ConnectionWrapper.hxx"
42cdf0e10cSrcweir #endif
43cdf0e10cSrcweir 
44cdf0e10cSrcweir 
45cdf0e10cSrcweir using namespace ::com::sun::star::uno;
46cdf0e10cSrcweir using namespace ::com::sun::star::lang;
47cdf0e10cSrcweir using namespace ::com::sun::star::sdbc;
48cdf0e10cSrcweir using namespace ::com::sun::star::beans;
49cdf0e10cSrcweir using namespace ::com::sun::star::container;
50cdf0e10cSrcweir using namespace ::osl;
51cdf0e10cSrcweir using namespace connectivity;
52cdf0e10cSrcweir 
53cdf0e10cSrcweir #include <algorithm>
54cdf0e10cSrcweir 
55cdf0e10cSrcweir //==========================================================================
56cdf0e10cSrcweir //= OPoolTimer
57cdf0e10cSrcweir //==========================================================================
onShot()58cdf0e10cSrcweir void SAL_CALL OPoolTimer::onShot()
59cdf0e10cSrcweir {
60cdf0e10cSrcweir 	m_pPool->invalidatePooledConnections();
61cdf0e10cSrcweir }
62cdf0e10cSrcweir namespace
63cdf0e10cSrcweir {
64cdf0e10cSrcweir 	//--------------------------------------------------------------------
getTimeoutNodeName()65cdf0e10cSrcweir 	static const ::rtl::OUString& getTimeoutNodeName()
66cdf0e10cSrcweir 	{
67cdf0e10cSrcweir 		static ::rtl::OUString s_sNodeName = ::rtl::OUString::createFromAscii("Timeout");
68cdf0e10cSrcweir 		return s_sNodeName;
69cdf0e10cSrcweir 	}
70*1636bfc2Smseidel 
71cdf0e10cSrcweir }
72cdf0e10cSrcweir //==========================================================================
73cdf0e10cSrcweir //= OConnectionPool
74cdf0e10cSrcweir //==========================================================================
75cdf0e10cSrcweir //--------------------------------------------------------------------------
OConnectionPool(const Reference<XDriver> & _xDriver,const Reference<XInterface> & _xDriverNode,const Reference<::com::sun::star::reflection::XProxyFactory> & _rxProxyFactory)76cdf0e10cSrcweir OConnectionPool::OConnectionPool(const Reference< XDriver >& _xDriver,
77cdf0e10cSrcweir 								 const Reference< XInterface >& _xDriverNode,
78cdf0e10cSrcweir 								 const Reference< ::com::sun::star::reflection::XProxyFactory >& _rxProxyFactory)
79cdf0e10cSrcweir 	:m_xDriver(_xDriver)
80cdf0e10cSrcweir 	,m_xDriverNode(_xDriverNode)
81cdf0e10cSrcweir 	,m_xProxyFactory(_rxProxyFactory)
82cdf0e10cSrcweir 	,m_nTimeOut(10)
83cdf0e10cSrcweir 	,m_nALiveCount(10)
84cdf0e10cSrcweir {
85cdf0e10cSrcweir 	OSL_ENSURE(m_xDriverNode.is(),"NO valid Driver node set!");
86cdf0e10cSrcweir 	Reference< XComponent >  xComponent(m_xDriverNode, UNO_QUERY);
87cdf0e10cSrcweir 	if (xComponent.is())
88cdf0e10cSrcweir 		xComponent->addEventListener(this);
89cdf0e10cSrcweir 
90cdf0e10cSrcweir 	Reference<XPropertySet> xProp(m_xDriverNode,UNO_QUERY);
91cdf0e10cSrcweir 	if(xProp.is())
92cdf0e10cSrcweir 		xProp->addPropertyChangeListener(getTimeoutNodeName(),this);
93cdf0e10cSrcweir 
94cdf0e10cSrcweir 	OPoolCollection::getNodeValue(getTimeoutNodeName(),m_xDriverNode) >>= m_nALiveCount;
95cdf0e10cSrcweir 	calculateTimeOuts();
96*1636bfc2Smseidel 
97cdf0e10cSrcweir 	m_xInvalidator = new OPoolTimer(this,::vos::TTimeValue(m_nTimeOut,0));
98cdf0e10cSrcweir 	m_xInvalidator->start();
99cdf0e10cSrcweir }
100cdf0e10cSrcweir // -----------------------------------------------------------------------------
~OConnectionPool()101cdf0e10cSrcweir OConnectionPool::~OConnectionPool()
102cdf0e10cSrcweir {
103cdf0e10cSrcweir 	clear(sal_False);
104cdf0e10cSrcweir }
105cdf0e10cSrcweir // -----------------------------------------------------------------------------
106cdf0e10cSrcweir struct TRemoveEventListenerFunctor : ::std::unary_function<TPooledConnections::value_type,void>
107cdf0e10cSrcweir 									,::std::unary_function<TActiveConnectionMap::value_type,void>
108cdf0e10cSrcweir {
109cdf0e10cSrcweir 	OConnectionPool* m_pConnectionPool;
110cdf0e10cSrcweir 	sal_Bool m_bDispose;
111cdf0e10cSrcweir 
TRemoveEventListenerFunctorTRemoveEventListenerFunctor112*1636bfc2Smseidel 	TRemoveEventListenerFunctor(OConnectionPool* _pConnectionPool,sal_Bool _bDispose = sal_False)
113cdf0e10cSrcweir 		: m_pConnectionPool(_pConnectionPool)
114cdf0e10cSrcweir 		,m_bDispose(_bDispose)
115cdf0e10cSrcweir 	{
116cdf0e10cSrcweir 		OSL_ENSURE(m_pConnectionPool,"No connection pool!");
117cdf0e10cSrcweir 	}
118cdf0e10cSrcweir 	// -----------------------------------------------------------------------------
disposeTRemoveEventListenerFunctor119cdf0e10cSrcweir 	void dispose(const Reference<XInterface>& _xComponent)
120cdf0e10cSrcweir 	{
121cdf0e10cSrcweir 		Reference< XComponent >  xComponent(_xComponent, UNO_QUERY);
122cdf0e10cSrcweir 
123cdf0e10cSrcweir 		if ( xComponent.is() )
124cdf0e10cSrcweir 		{
125cdf0e10cSrcweir 			xComponent->removeEventListener(m_pConnectionPool);
126cdf0e10cSrcweir 			if ( m_bDispose )
127cdf0e10cSrcweir 				xComponent->dispose();
128cdf0e10cSrcweir 		}
129cdf0e10cSrcweir 	}
130cdf0e10cSrcweir 	// -----------------------------------------------------------------------------
operator ()TRemoveEventListenerFunctor131cdf0e10cSrcweir 	void operator()(const TPooledConnections::value_type& _aValue)
132cdf0e10cSrcweir 	{
133cdf0e10cSrcweir 		dispose(_aValue);
134cdf0e10cSrcweir 	}
135cdf0e10cSrcweir 	// -----------------------------------------------------------------------------
operator ()TRemoveEventListenerFunctor136cdf0e10cSrcweir 	void operator()(const TActiveConnectionMap::value_type& _aValue)
137cdf0e10cSrcweir 	{
138cdf0e10cSrcweir 		dispose(_aValue.first);
139cdf0e10cSrcweir 	}
140cdf0e10cSrcweir };
141cdf0e10cSrcweir // -----------------------------------------------------------------------------
142cdf0e10cSrcweir struct TConnectionPoolFunctor : ::std::unary_function<TConnectionMap::value_type,void>
143cdf0e10cSrcweir {
144cdf0e10cSrcweir 	OConnectionPool* m_pConnectionPool;
145cdf0e10cSrcweir 
TConnectionPoolFunctorTConnectionPoolFunctor146*1636bfc2Smseidel 	TConnectionPoolFunctor(OConnectionPool* _pConnectionPool)
147cdf0e10cSrcweir 		: m_pConnectionPool(_pConnectionPool)
148cdf0e10cSrcweir 	{
149cdf0e10cSrcweir 		OSL_ENSURE(m_pConnectionPool,"No connection pool!");
150cdf0e10cSrcweir 	}
operator ()TConnectionPoolFunctor151cdf0e10cSrcweir 	void operator()(const TConnectionMap::value_type& _aValue)
152cdf0e10cSrcweir 	{
153cdf0e10cSrcweir 		::std::for_each(_aValue.second.aConnections.begin(),_aValue.second.aConnections.end(),TRemoveEventListenerFunctor(m_pConnectionPool,sal_True));
154cdf0e10cSrcweir 	}
155cdf0e10cSrcweir };
156cdf0e10cSrcweir // -----------------------------------------------------------------------------
clear(sal_Bool _bDispose)157cdf0e10cSrcweir void OConnectionPool::clear(sal_Bool _bDispose)
158cdf0e10cSrcweir {
159cdf0e10cSrcweir 	MutexGuard aGuard(m_aMutex);
160cdf0e10cSrcweir 
161cdf0e10cSrcweir 	if(m_xInvalidator->isTicking())
162cdf0e10cSrcweir 		m_xInvalidator->stop();
163cdf0e10cSrcweir 
164cdf0e10cSrcweir 	::std::for_each(m_aPool.begin(),m_aPool.end(),TConnectionPoolFunctor(this));
165cdf0e10cSrcweir 	m_aPool.clear();
166cdf0e10cSrcweir 
167cdf0e10cSrcweir 	::std::for_each(m_aActiveConnections.begin(),m_aActiveConnections.end(),TRemoveEventListenerFunctor(this,_bDispose));
168cdf0e10cSrcweir 	m_aActiveConnections.clear();
169cdf0e10cSrcweir 
170cdf0e10cSrcweir 	Reference< XComponent >  xComponent(m_xDriverNode, UNO_QUERY);
171cdf0e10cSrcweir 	if (xComponent.is())
172cdf0e10cSrcweir 		xComponent->removeEventListener(this);
173cdf0e10cSrcweir 	Reference< XPropertySet >  xProp(m_xDriverNode, UNO_QUERY);
174cdf0e10cSrcweir 	if (xProp.is())
175cdf0e10cSrcweir 		xProp->removePropertyChangeListener(getTimeoutNodeName(),this);
176cdf0e10cSrcweir 
177cdf0e10cSrcweir m_xDriverNode.clear();
178cdf0e10cSrcweir m_xDriver.clear();
179cdf0e10cSrcweir }
180cdf0e10cSrcweir //--------------------------------------------------------------------------
getConnectionWithInfo(const::rtl::OUString & _rURL,const Sequence<PropertyValue> & _rInfo)181cdf0e10cSrcweir Reference< XConnection > SAL_CALL OConnectionPool::getConnectionWithInfo( const ::rtl::OUString& _rURL, const Sequence< PropertyValue >& _rInfo ) throw(SQLException, RuntimeException)
182cdf0e10cSrcweir {
183cdf0e10cSrcweir 	MutexGuard aGuard(m_aMutex);
184cdf0e10cSrcweir 
185cdf0e10cSrcweir 	Reference<XConnection> xConnection;
186cdf0e10cSrcweir 
187cdf0e10cSrcweir 	// create a unique id and look for it in our map
188cdf0e10cSrcweir 	Sequence< PropertyValue > aInfo(_rInfo);
189cdf0e10cSrcweir 	TConnectionMap::key_type nId;
190cdf0e10cSrcweir 	OConnectionWrapper::createUniqueId(_rURL,aInfo,nId.m_pBuffer);
191cdf0e10cSrcweir 	TConnectionMap::iterator aIter = m_aPool.find(nId);
192cdf0e10cSrcweir 
193cdf0e10cSrcweir 	if ( m_aPool.end() != aIter )
194cdf0e10cSrcweir 		xConnection = getPooledConnection(aIter);
195cdf0e10cSrcweir 
196cdf0e10cSrcweir 	if ( !xConnection.is() )
197cdf0e10cSrcweir 		xConnection = createNewConnection(_rURL,_rInfo);
198*1636bfc2Smseidel 
199cdf0e10cSrcweir 	return xConnection;
200cdf0e10cSrcweir }
201cdf0e10cSrcweir //--------------------------------------------------------------------------
disposing(const::com::sun::star::lang::EventObject & Source)202cdf0e10cSrcweir void SAL_CALL OConnectionPool::disposing( const ::com::sun::star::lang::EventObject& Source ) throw (RuntimeException)
203cdf0e10cSrcweir {
204cdf0e10cSrcweir 	Reference<XConnection> xConnection(Source.Source,UNO_QUERY);
205cdf0e10cSrcweir 	if(xConnection.is())
206cdf0e10cSrcweir 	{
207cdf0e10cSrcweir 		MutexGuard aGuard(m_aMutex);
208cdf0e10cSrcweir 		TActiveConnectionMap::iterator aIter = m_aActiveConnections.find(xConnection);
209*1636bfc2Smseidel 		OSL_ENSURE(aIter != m_aActiveConnections.end(),"OConnectionPool::disposing: Connection wasn't in pool");
210cdf0e10cSrcweir 		if(aIter != m_aActiveConnections.end())
211cdf0e10cSrcweir 		{ // move the pooled connection back to the pool
212cdf0e10cSrcweir 			aIter->second.aPos->second.nALiveCount = m_nALiveCount;
213cdf0e10cSrcweir 			aIter->second.aPos->second.aConnections.push_back(aIter->second.xPooledConnection);
214cdf0e10cSrcweir 			m_aActiveConnections.erase(aIter);
215cdf0e10cSrcweir 		}
216cdf0e10cSrcweir 	}
217cdf0e10cSrcweir 	else
218cdf0e10cSrcweir 	{
219cdf0e10cSrcweir 	m_xDriverNode.clear();
220cdf0e10cSrcweir 	}
221cdf0e10cSrcweir }
222cdf0e10cSrcweir // -----------------------------------------------------------------------------
createNewConnection(const::rtl::OUString & _rURL,const Sequence<PropertyValue> & _rInfo)223cdf0e10cSrcweir Reference< XConnection> OConnectionPool::createNewConnection(const ::rtl::OUString& _rURL,const Sequence< PropertyValue >& _rInfo)
224cdf0e10cSrcweir {
225*1636bfc2Smseidel 	// create new pooled connection
226cdf0e10cSrcweir 	Reference< XPooledConnection > xPooledConnection = new ::connectivity::OPooledConnection(m_xDriver->connect(_rURL,_rInfo),m_xProxyFactory);
227cdf0e10cSrcweir 	// get the new connection from the pooled connection
228cdf0e10cSrcweir 	Reference<XConnection> xConnection = xPooledConnection->getConnection();
229cdf0e10cSrcweir 	if(xConnection.is())
230cdf0e10cSrcweir 	{
231cdf0e10cSrcweir 		// add our own as dispose listener to know when we should put the connection back to the pool
232cdf0e10cSrcweir 		Reference< XComponent >  xComponent(xConnection, UNO_QUERY);
233cdf0e10cSrcweir 		if (xComponent.is())
234cdf0e10cSrcweir 			xComponent->addEventListener(this);
235cdf0e10cSrcweir 
236cdf0e10cSrcweir 		// save some information to find the right pool later on
237cdf0e10cSrcweir 		Sequence< PropertyValue > aInfo(_rInfo);
238cdf0e10cSrcweir 		TConnectionMap::key_type nId;
239cdf0e10cSrcweir 		OConnectionWrapper::createUniqueId(_rURL,aInfo,nId.m_pBuffer);
240cdf0e10cSrcweir 		TConnectionPool aPack;
241*1636bfc2Smseidel 
242cdf0e10cSrcweir 		// insert the new connection and struct into the active connection map
243cdf0e10cSrcweir 		aPack.nALiveCount				= m_nALiveCount;
244cdf0e10cSrcweir 		TActiveConnectionInfo aActiveInfo;
245cdf0e10cSrcweir 		aActiveInfo.aPos				= m_aPool.insert(TConnectionMap::value_type(nId,aPack)).first;
246cdf0e10cSrcweir 		aActiveInfo.xPooledConnection	= xPooledConnection;
247cdf0e10cSrcweir 		m_aActiveConnections.insert(TActiveConnectionMap::value_type(xConnection,aActiveInfo));
248cdf0e10cSrcweir 
249cdf0e10cSrcweir 		if(m_xInvalidator->isExpired())
250cdf0e10cSrcweir 			m_xInvalidator->start();
251cdf0e10cSrcweir 	}
252cdf0e10cSrcweir 
253cdf0e10cSrcweir 	return xConnection;
254cdf0e10cSrcweir }
255cdf0e10cSrcweir // -----------------------------------------------------------------------------
invalidatePooledConnections()256cdf0e10cSrcweir void OConnectionPool::invalidatePooledConnections()
257cdf0e10cSrcweir {
258cdf0e10cSrcweir 	MutexGuard aGuard(m_aMutex);
259cdf0e10cSrcweir 	TConnectionMap::iterator aIter = m_aPool.begin();
260cdf0e10cSrcweir 	for (; aIter != m_aPool.end(); )
261cdf0e10cSrcweir 	{
262*1636bfc2Smseidel 		if(!(--(aIter->second.nALiveCount))) // connections are invalid
263cdf0e10cSrcweir 		{
264cdf0e10cSrcweir 			::std::for_each(aIter->second.aConnections.begin(),aIter->second.aConnections.end(),TRemoveEventListenerFunctor(this,sal_True));
265cdf0e10cSrcweir 
266cdf0e10cSrcweir 			aIter->second.aConnections.clear();
267cdf0e10cSrcweir 
268cdf0e10cSrcweir 			// look if the iterator aIter is still present in the active connection map
269cdf0e10cSrcweir 			TActiveConnectionMap::iterator aActIter = m_aActiveConnections.begin();
270cdf0e10cSrcweir 			for (; aActIter != m_aActiveConnections.end(); ++aActIter)
271cdf0e10cSrcweir 			{
272cdf0e10cSrcweir 				if(aIter == aActIter->second.aPos)
273cdf0e10cSrcweir 					break;
274cdf0e10cSrcweir 			}
275cdf0e10cSrcweir 			if(aActIter == m_aActiveConnections.end())
276cdf0e10cSrcweir 			{// he isn't so we can delete him
277cdf0e10cSrcweir 				TConnectionMap::iterator aDeleteIter = aIter;
278cdf0e10cSrcweir 				++aIter;
279cdf0e10cSrcweir 				m_aPool.erase(aDeleteIter);
280cdf0e10cSrcweir 			}
281cdf0e10cSrcweir 			else
282cdf0e10cSrcweir 				++aIter;
283cdf0e10cSrcweir 		}
284cdf0e10cSrcweir 		else
285cdf0e10cSrcweir 			++aIter;
286cdf0e10cSrcweir 	}
287cdf0e10cSrcweir 	if(!m_aPool.empty())
288cdf0e10cSrcweir 		m_xInvalidator->start();
289cdf0e10cSrcweir }
290cdf0e10cSrcweir // -----------------------------------------------------------------------------
getPooledConnection(TConnectionMap::iterator & _rIter)291cdf0e10cSrcweir Reference< XConnection> OConnectionPool::getPooledConnection(TConnectionMap::iterator& _rIter)
292cdf0e10cSrcweir {
293cdf0e10cSrcweir 	Reference<XConnection> xConnection;
294cdf0e10cSrcweir 
295cdf0e10cSrcweir 	if(!_rIter->second.aConnections.empty())
296cdf0e10cSrcweir 	{
297cdf0e10cSrcweir 		Reference< XPooledConnection > xPooledConnection = _rIter->second.aConnections.back();
298cdf0e10cSrcweir 		_rIter->second.aConnections.pop_back();
299cdf0e10cSrcweir 
300cdf0e10cSrcweir 		OSL_ENSURE(xPooledConnection.is(),"Can not be null here!");
301cdf0e10cSrcweir 		xConnection = xPooledConnection->getConnection();
302cdf0e10cSrcweir 		Reference< XComponent >  xComponent(xConnection, UNO_QUERY);
303cdf0e10cSrcweir 		if (xComponent.is())
304cdf0e10cSrcweir 			xComponent->addEventListener(this);
305*1636bfc2Smseidel 
306cdf0e10cSrcweir 		TActiveConnectionInfo aActiveInfo;
307cdf0e10cSrcweir 		aActiveInfo.aPos = _rIter;
308cdf0e10cSrcweir 		aActiveInfo.xPooledConnection = xPooledConnection;
309cdf0e10cSrcweir 		m_aActiveConnections[xConnection] = aActiveInfo;
310cdf0e10cSrcweir 	}
311cdf0e10cSrcweir 	return xConnection;
312cdf0e10cSrcweir }
313cdf0e10cSrcweir // -----------------------------------------------------------------------------
propertyChange(const PropertyChangeEvent & evt)314cdf0e10cSrcweir void SAL_CALL OConnectionPool::propertyChange( const PropertyChangeEvent& evt ) throw (::com::sun::star::uno::RuntimeException)
315cdf0e10cSrcweir {
316cdf0e10cSrcweir 	if(getTimeoutNodeName() == evt.PropertyName)
317cdf0e10cSrcweir 	{
318cdf0e10cSrcweir 		evt.NewValue >>= m_nALiveCount;
319cdf0e10cSrcweir 		calculateTimeOuts();
320cdf0e10cSrcweir 	}
321cdf0e10cSrcweir }
322cdf0e10cSrcweir // -----------------------------------------------------------------------------
calculateTimeOuts()323cdf0e10cSrcweir void OConnectionPool::calculateTimeOuts()
324cdf0e10cSrcweir {
325cdf0e10cSrcweir 	sal_Int32 nTimeOutCorrection = 10;
326cdf0e10cSrcweir 	if(m_nALiveCount < 100)
327cdf0e10cSrcweir 		nTimeOutCorrection = 20;
328cdf0e10cSrcweir 
329cdf0e10cSrcweir 	m_nTimeOut		= m_nALiveCount / nTimeOutCorrection;
330cdf0e10cSrcweir 	m_nALiveCount	= m_nALiveCount / m_nTimeOut;
331cdf0e10cSrcweir }
332cdf0e10cSrcweir // -----------------------------------------------------------------------------
333*1636bfc2Smseidel 
334*1636bfc2Smseidel /* vim: set noet sw=4 ts=4: */
335