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 :
21 : #include "dp_registry.hrc"
22 : #include "dp_misc.h"
23 : #include "dp_resource.h"
24 : #include "dp_interact.h"
25 : #include "dp_ucb.h"
26 : #include "osl/diagnose.h"
27 : #include "rtl/ustrbuf.hxx"
28 : #include "rtl/uri.hxx"
29 : #include "cppuhelper/compbase2.hxx"
30 : #include "cppuhelper/exc_hlp.hxx"
31 : #include "comphelper/sequence.hxx"
32 : #include "ucbhelper/content.hxx"
33 : #include "com/sun/star/uno/DeploymentException.hpp"
34 : #include "com/sun/star/lang/DisposedException.hpp"
35 : #include "com/sun/star/lang/WrappedTargetRuntimeException.hpp"
36 : #include "com/sun/star/lang/XServiceInfo.hpp"
37 : #include "com/sun/star/lang/XSingleComponentFactory.hpp"
38 : #include "com/sun/star/lang/XSingleServiceFactory.hpp"
39 : #include "com/sun/star/util/XUpdatable.hpp"
40 : #include "com/sun/star/container/XContentEnumerationAccess.hpp"
41 : #include "com/sun/star/deployment/PackageRegistryBackend.hpp"
42 : #include <boost/unordered_map.hpp>
43 : #include <set>
44 : #include <boost/unordered_set.hpp>
45 : #include <memory>
46 :
47 : using namespace ::dp_misc;
48 : using namespace ::com::sun::star;
49 : using namespace ::com::sun::star::uno;
50 : using namespace ::com::sun::star::ucb;
51 : using ::rtl::OUString;
52 :
53 :
54 : namespace dp_registry {
55 :
56 : namespace backend {
57 : namespace bundle {
58 : Reference<deployment::XPackageRegistry> create(
59 : Reference<deployment::XPackageRegistry> const & xRootRegistry,
60 : OUString const & context, OUString const & cachePath, bool readOnly,
61 : Reference<XComponentContext> const & xComponentContext );
62 : }
63 : }
64 :
65 : namespace {
66 :
67 : typedef ::cppu::WeakComponentImplHelper2<
68 : deployment::XPackageRegistry, util::XUpdatable > t_helper;
69 :
70 : //==============================================================================
71 : class PackageRegistryImpl : private MutexHolder, public t_helper
72 : {
73 : struct ci_string_hash {
74 0 : ::std::size_t operator () ( OUString const & str ) const {
75 0 : return str.toAsciiLowerCase().hashCode();
76 : }
77 : };
78 : struct ci_string_equals {
79 0 : bool operator () ( OUString const & str1, OUString const & str2 ) const{
80 0 : return str1.equalsIgnoreAsciiCase( str2 );
81 : }
82 : };
83 : typedef ::boost::unordered_map<
84 : OUString, Reference<deployment::XPackageRegistry>,
85 : ci_string_hash, ci_string_equals > t_string2registry;
86 : typedef ::boost::unordered_map<
87 : OUString, OUString,
88 : ci_string_hash, ci_string_equals > t_string2string;
89 : typedef ::std::set<
90 : Reference<deployment::XPackageRegistry> > t_registryset;
91 :
92 : t_string2registry m_mediaType2backend;
93 : t_string2string m_filter2mediaType;
94 : t_registryset m_ambiguousBackends;
95 : t_registryset m_allBackends;
96 : ::std::vector< Reference<deployment::XPackageTypeInfo> > m_typesInfos;
97 :
98 : void insertBackend(
99 : Reference<deployment::XPackageRegistry> const & xBackend );
100 :
101 : protected:
102 : inline void check();
103 : virtual void SAL_CALL disposing();
104 :
105 : virtual ~PackageRegistryImpl();
106 0 : PackageRegistryImpl() : t_helper( getMutex() ) {}
107 :
108 :
109 : public:
110 : static Reference<deployment::XPackageRegistry> create(
111 : OUString const & context,
112 : OUString const & cachePath, bool readOnly,
113 : Reference<XComponentContext> const & xComponentContext );
114 :
115 : // XUpdatable
116 : virtual void SAL_CALL update() throw (RuntimeException);
117 :
118 : // XPackageRegistry
119 : virtual Reference<deployment::XPackage> SAL_CALL bindPackage(
120 : OUString const & url, OUString const & mediaType, sal_Bool bRemoved,
121 : OUString const & identifier, Reference<XCommandEnvironment> const & xCmdEnv )
122 : throw (deployment::DeploymentException,
123 : deployment::InvalidRemovedParameterException,
124 : CommandFailedException,
125 : lang::IllegalArgumentException, RuntimeException);
126 : virtual Sequence< Reference<deployment::XPackageTypeInfo> > SAL_CALL
127 : getSupportedPackageTypes() throw (RuntimeException);
128 : virtual void SAL_CALL packageRemoved(OUString const & url, OUString const & mediaType)
129 : throw (deployment::DeploymentException,
130 : RuntimeException);
131 :
132 : };
133 :
134 : //______________________________________________________________________________
135 0 : inline void PackageRegistryImpl::check()
136 : {
137 0 : ::osl::MutexGuard guard( getMutex() );
138 0 : if (rBHelper.bInDispose || rBHelper.bDisposed) {
139 : throw lang::DisposedException(
140 : OUSTR("PackageRegistry instance has already been disposed!"),
141 0 : static_cast<OWeakObject *>(this) );
142 0 : }
143 0 : }
144 :
145 : //______________________________________________________________________________
146 0 : void PackageRegistryImpl::disposing()
147 : {
148 : // dispose all backends:
149 0 : t_registryset::const_iterator iPos( m_allBackends.begin() );
150 0 : t_registryset::const_iterator const iEnd( m_allBackends.end() );
151 0 : for ( ; iPos != iEnd; ++iPos ) {
152 0 : try_dispose( *iPos );
153 : }
154 0 : m_mediaType2backend = t_string2registry();
155 0 : m_ambiguousBackends = t_registryset();
156 0 : m_allBackends = t_registryset();
157 :
158 0 : t_helper::disposing();
159 0 : }
160 :
161 : //______________________________________________________________________________
162 0 : PackageRegistryImpl::~PackageRegistryImpl()
163 : {
164 0 : }
165 :
166 : //______________________________________________________________________________
167 0 : OUString normalizeMediaType( OUString const & mediaType )
168 : {
169 0 : ::rtl::OUStringBuffer buf;
170 0 : sal_Int32 index = 0;
171 0 : for (;;) {
172 0 : buf.append( mediaType.getToken( 0, '/', index ).trim() );
173 0 : if (index < 0)
174 0 : break;
175 0 : buf.append( static_cast< sal_Unicode >('/') );
176 : }
177 0 : return buf.makeStringAndClear();
178 : }
179 :
180 : //______________________________________________________________________________
181 :
182 0 : void PackageRegistryImpl::packageRemoved(
183 : ::rtl::OUString const & url, ::rtl::OUString const & mediaType)
184 : throw (css::deployment::DeploymentException,
185 : css::uno::RuntimeException)
186 : {
187 : const t_string2registry::const_iterator i =
188 0 : m_mediaType2backend.find(mediaType);
189 :
190 0 : if (i != m_mediaType2backend.end())
191 : {
192 0 : i->second->packageRemoved(url, mediaType);
193 : }
194 0 : }
195 :
196 0 : void PackageRegistryImpl::insertBackend(
197 : Reference<deployment::XPackageRegistry> const & xBackend )
198 : {
199 0 : m_allBackends.insert( xBackend );
200 : typedef ::boost::unordered_set<OUString, ::rtl::OUStringHash> t_stringset;
201 0 : t_stringset ambiguousFilters;
202 :
203 : const Sequence< Reference<deployment::XPackageTypeInfo> > packageTypes(
204 0 : xBackend->getSupportedPackageTypes() );
205 0 : for ( sal_Int32 pos = 0; pos < packageTypes.getLength(); ++pos )
206 : {
207 : Reference<deployment::XPackageTypeInfo> const & xPackageType =
208 0 : packageTypes[ pos ];
209 0 : m_typesInfos.push_back( xPackageType );
210 :
211 : const OUString mediaType( normalizeMediaType(
212 0 : xPackageType->getMediaType() ) );
213 : ::std::pair<t_string2registry::iterator, bool> mb_insertion(
214 : m_mediaType2backend.insert( t_string2registry::value_type(
215 0 : mediaType, xBackend ) ) );
216 0 : if (mb_insertion.second)
217 : {
218 : // add parameterless media-type, too:
219 0 : sal_Int32 semi = mediaType.indexOf( ';' );
220 0 : if (semi >= 0) {
221 : m_mediaType2backend.insert(
222 : t_string2registry::value_type(
223 0 : mediaType.copy( 0, semi ), xBackend ) );
224 : }
225 0 : const OUString fileFilter( xPackageType->getFileFilter() );
226 : //The package backend shall also be called to determine the mediatype
227 : //(XPackageRegistry.bindPackage) when the URL points to a directory.
228 0 : const bool bExtension = mediaType.equals(OUSTR("application/vnd.sun.star.package-bundle"));
229 0 : if (fileFilter.isEmpty() || fileFilter == "*.*" || fileFilter == "*" || bExtension)
230 : {
231 0 : m_ambiguousBackends.insert( xBackend );
232 : }
233 : else
234 : {
235 0 : sal_Int32 nIndex = 0;
236 0 : do {
237 0 : OUString token( fileFilter.getToken( 0, ';', nIndex ) );
238 0 : if (token.matchAsciiL( RTL_CONSTASCII_STRINGPARAM("*.") ))
239 0 : token = token.copy( 1 );
240 0 : if (token.isEmpty())
241 0 : continue;
242 : // mark any further wildcards ambig:
243 0 : bool ambig = (token.indexOf('*') >= 0 ||
244 0 : token.indexOf('?') >= 0);
245 0 : if (! ambig) {
246 : ::std::pair<t_string2string::iterator, bool> ins(
247 : m_filter2mediaType.insert(
248 : t_string2string::value_type(
249 0 : token, mediaType ) ) );
250 0 : ambig = !ins.second;
251 0 : if (ambig) {
252 : // filter has already been in: add previously
253 : // added backend to ambig set
254 : const t_string2registry::const_iterator iFind(
255 : m_mediaType2backend.find(
256 : /* media-type of pr. added backend */
257 0 : ins.first->second ) );
258 : OSL_ASSERT(
259 : iFind != m_mediaType2backend.end() );
260 0 : if (iFind != m_mediaType2backend.end())
261 0 : m_ambiguousBackends.insert( iFind->second );
262 : }
263 : }
264 0 : if (ambig) {
265 0 : m_ambiguousBackends.insert( xBackend );
266 : // mark filter to be removed later from filters map:
267 0 : ambiguousFilters.insert( token );
268 0 : }
269 : }
270 : while (nIndex >= 0);
271 0 : }
272 : }
273 : #if OSL_DEBUG_LEVEL > 0
274 : else
275 : {
276 : ::rtl::OUStringBuffer buf;
277 : buf.appendAscii(
278 : RTL_CONSTASCII_STRINGPARAM(
279 : "more than one PackageRegistryBackend for "
280 : "media-type=\"") );
281 : buf.append( mediaType );
282 : buf.appendAscii( RTL_CONSTASCII_STRINGPARAM("\" => ") );
283 : buf.append( Reference<lang::XServiceInfo>(
284 : xBackend, UNO_QUERY_THROW )->
285 : getImplementationName() );
286 : buf.appendAscii( RTL_CONSTASCII_STRINGPARAM("\"!") );
287 : OSL_FAIL( ::rtl::OUStringToOString(
288 : buf.makeStringAndClear(),
289 : RTL_TEXTENCODING_UTF8).getStr() );
290 : }
291 : #endif
292 0 : }
293 :
294 : // cut out ambiguous filters:
295 0 : t_stringset::const_iterator iPos( ambiguousFilters.begin() );
296 0 : const t_stringset::const_iterator iEnd( ambiguousFilters.end() );
297 0 : for ( ; iPos != iEnd; ++iPos ) {
298 0 : m_filter2mediaType.erase( *iPos );
299 0 : }
300 0 : }
301 :
302 : //______________________________________________________________________________
303 0 : Reference<deployment::XPackageRegistry> PackageRegistryImpl::create(
304 : OUString const & context,
305 : OUString const & cachePath, bool readOnly,
306 : Reference<XComponentContext> const & xComponentContext )
307 : {
308 0 : PackageRegistryImpl * that = new PackageRegistryImpl;
309 0 : Reference<deployment::XPackageRegistry> xRet(that);
310 :
311 : // auto-detect all registered package registries:
312 : Reference<container::XEnumeration> xEnum(
313 : Reference<container::XContentEnumerationAccess>(
314 0 : xComponentContext->getServiceManager(),
315 0 : UNO_QUERY_THROW )->createContentEnumeration(
316 0 : OUSTR("com.sun.star.deployment.PackageRegistryBackend") ) );
317 0 : if (xEnum.is())
318 : {
319 0 : while (xEnum->hasMoreElements())
320 : {
321 0 : Any element( xEnum->nextElement() );
322 0 : Sequence<Any> registryArgs(cachePath.isEmpty() ? 1 : 3 );
323 0 : registryArgs[ 0 ] <<= context;
324 0 : if (!cachePath.isEmpty())
325 : {
326 : Reference<lang::XServiceInfo> xServiceInfo(
327 0 : element, UNO_QUERY_THROW );
328 : OUString registryCachePath(
329 : makeURL( cachePath,
330 : ::rtl::Uri::encode(
331 0 : xServiceInfo->getImplementationName(),
332 : rtl_UriCharClassPchar,
333 : rtl_UriEncodeIgnoreEscapes,
334 0 : RTL_TEXTENCODING_UTF8 ) ) );
335 0 : registryArgs[ 1 ] <<= registryCachePath;
336 0 : registryArgs[ 2 ] <<= readOnly;
337 0 : if (! readOnly)
338 : create_folder( 0, registryCachePath,
339 0 : Reference<XCommandEnvironment>() );
340 : }
341 :
342 0 : Reference<deployment::XPackageRegistry> xBackend;
343 0 : Reference<lang::XSingleComponentFactory> xFac( element, UNO_QUERY );
344 0 : if (xFac.is()) {
345 : xBackend.set(
346 0 : xFac->createInstanceWithArgumentsAndContext(
347 0 : registryArgs, xComponentContext ), UNO_QUERY );
348 : }
349 : else {
350 : Reference<lang::XSingleServiceFactory> xSingleServiceFac(
351 0 : element, UNO_QUERY_THROW );
352 : xBackend.set(
353 0 : xSingleServiceFac->createInstanceWithArguments(
354 0 : registryArgs ), UNO_QUERY );
355 : }
356 0 : if (! xBackend.is()) {
357 : throw DeploymentException(
358 : OUSTR("cannot instantiate PackageRegistryBackend service: ")
359 : + Reference<lang::XServiceInfo>(
360 0 : element, UNO_QUERY_THROW )->getImplementationName(),
361 0 : static_cast<OWeakObject *>(that) );
362 : }
363 :
364 0 : that->insertBackend( xBackend );
365 0 : }
366 : }
367 :
368 : // Insert bundle back-end.
369 : // Always register as last, because we want to add extensions also as folders
370 : // and as a default we accept every folder, which was not recognized by the other
371 : // backends.
372 : Reference<deployment::XPackageRegistry> extensionBackend =
373 : ::dp_registry::backend::bundle::create(
374 0 : that, context, cachePath, readOnly, xComponentContext);
375 0 : that->insertBackend(extensionBackend);
376 :
377 : Reference<lang::XServiceInfo> xServiceInfo(
378 0 : extensionBackend, UNO_QUERY_THROW );
379 :
380 : OSL_ASSERT(xServiceInfo.is());
381 : OUString registryCachePath(
382 : makeURL( cachePath,
383 : ::rtl::Uri::encode(
384 0 : xServiceInfo->getImplementationName(),
385 : rtl_UriCharClassPchar,
386 : rtl_UriEncodeIgnoreEscapes,
387 0 : RTL_TEXTENCODING_UTF8 ) ) );
388 0 : create_folder( 0, registryCachePath, Reference<XCommandEnvironment>());
389 :
390 :
391 : #if OSL_DEBUG_LEVEL > 1
392 : // dump tables:
393 : {
394 : t_registryset allBackends;
395 : dp_misc::TRACE("> [dp_registry.cxx] media-type detection:\n\n" );
396 : for ( t_string2string::const_iterator iPos(
397 : that->m_filter2mediaType.begin() );
398 : iPos != that->m_filter2mediaType.end(); ++iPos )
399 : {
400 : ::rtl::OUStringBuffer buf;
401 : buf.appendAscii( RTL_CONSTASCII_STRINGPARAM("extension \"") );
402 : buf.append( iPos->first );
403 : buf.appendAscii( RTL_CONSTASCII_STRINGPARAM(
404 : "\" maps to media-type \"") );
405 : buf.append( iPos->second );
406 : buf.appendAscii( RTL_CONSTASCII_STRINGPARAM(
407 : "\" maps to backend ") );
408 : const Reference<deployment::XPackageRegistry> xBackend(
409 : that->m_mediaType2backend.find( iPos->second )->second );
410 : allBackends.insert( xBackend );
411 : buf.append( Reference<lang::XServiceInfo>(
412 : xBackend, UNO_QUERY_THROW )
413 : ->getImplementationName() );
414 : dp_misc::writeConsole( buf.makeStringAndClear() + OUSTR("\n"));
415 : }
416 : dp_misc::TRACE( "> [dp_registry.cxx] ambiguous backends:\n\n" );
417 : for ( t_registryset::const_iterator iPos(
418 : that->m_ambiguousBackends.begin() );
419 : iPos != that->m_ambiguousBackends.end(); ++iPos )
420 : {
421 : ::rtl::OUStringBuffer buf;
422 : buf.append(
423 : Reference<lang::XServiceInfo>(
424 : *iPos, UNO_QUERY_THROW )->getImplementationName() );
425 : buf.appendAscii( RTL_CONSTASCII_STRINGPARAM(": ") );
426 : const Sequence< Reference<deployment::XPackageTypeInfo> > types(
427 : (*iPos)->getSupportedPackageTypes() );
428 : for ( sal_Int32 pos = 0; pos < types.getLength(); ++pos ) {
429 : Reference<deployment::XPackageTypeInfo> const & xInfo =
430 : types[ pos ];
431 : buf.append( xInfo->getMediaType() );
432 : const OUString filter( xInfo->getFileFilter() );
433 : if (!filter.isEmpty()) {
434 : buf.appendAscii( RTL_CONSTASCII_STRINGPARAM(" (") );
435 : buf.append( filter );
436 : buf.appendAscii( RTL_CONSTASCII_STRINGPARAM(")") );
437 : }
438 : if (pos < (types.getLength() - 1))
439 : buf.appendAscii( RTL_CONSTASCII_STRINGPARAM(", ") );
440 : }
441 : dp_misc::TRACE(buf.makeStringAndClear() + OUSTR("\n\n"));
442 : }
443 : allBackends.insert( that->m_ambiguousBackends.begin(),
444 : that->m_ambiguousBackends.end() );
445 : OSL_ASSERT( allBackends == that->m_allBackends );
446 : }
447 : #endif
448 :
449 0 : return xRet;
450 : }
451 :
452 : // XUpdatable: broadcast to backends
453 : //______________________________________________________________________________
454 0 : void PackageRegistryImpl::update() throw (RuntimeException)
455 : {
456 0 : check();
457 0 : t_registryset::const_iterator iPos( m_allBackends.begin() );
458 0 : const t_registryset::const_iterator iEnd( m_allBackends.end() );
459 0 : for ( ; iPos != iEnd; ++iPos ) {
460 0 : const Reference<util::XUpdatable> xUpdatable( *iPos, UNO_QUERY );
461 0 : if (xUpdatable.is())
462 0 : xUpdatable->update();
463 0 : }
464 0 : }
465 :
466 : // XPackageRegistry
467 : //______________________________________________________________________________
468 0 : Reference<deployment::XPackage> PackageRegistryImpl::bindPackage(
469 : OUString const & url, OUString const & mediaType_, sal_Bool bRemoved,
470 : OUString const & identifier, Reference<XCommandEnvironment> const & xCmdEnv )
471 : throw (deployment::DeploymentException, deployment::InvalidRemovedParameterException,
472 : CommandFailedException,
473 : lang::IllegalArgumentException, RuntimeException)
474 : {
475 0 : check();
476 0 : OUString mediaType(mediaType_);
477 0 : if (mediaType.isEmpty())
478 : {
479 0 : ::ucbhelper::Content ucbContent;
480 0 : if (create_ucb_content(
481 0 : &ucbContent, url, xCmdEnv, false /* no throw */ )
482 0 : && !ucbContent.isFolder())
483 : {
484 0 : OUString title( StrTitle::getTitle( ucbContent ) );
485 0 : for (;;)
486 : {
487 : const t_string2string::const_iterator iFind(
488 0 : m_filter2mediaType.find(title) );
489 0 : if (iFind != m_filter2mediaType.end()) {
490 0 : mediaType = iFind->second;
491 : break;
492 : }
493 0 : sal_Int32 point = title.indexOf( '.', 1 /* consume . */ );
494 0 : if (point < 0)
495 : break;
496 0 : title = title.copy(point);
497 0 : }
498 0 : }
499 : }
500 0 : if (mediaType.isEmpty())
501 : {
502 : // try ambiguous backends:
503 0 : t_registryset::const_iterator iPos( m_ambiguousBackends.begin() );
504 0 : const t_registryset::const_iterator iEnd( m_ambiguousBackends.end() );
505 0 : for ( ; iPos != iEnd; ++iPos )
506 : {
507 : try {
508 0 : return (*iPos)->bindPackage( url, mediaType, bRemoved,
509 0 : identifier, xCmdEnv );
510 : }
511 0 : catch (const lang::IllegalArgumentException &) {
512 : }
513 : }
514 : throw lang::IllegalArgumentException(
515 0 : getResourceString(RID_STR_CANNOT_DETECT_MEDIA_TYPE) + url,
516 0 : static_cast<OWeakObject *>(this), static_cast<sal_Int16>(-1) );
517 : }
518 : else
519 : {
520 : // get backend by media-type:
521 : t_string2registry::const_iterator iFind(
522 0 : m_mediaType2backend.find( normalizeMediaType(mediaType) ) );
523 0 : if (iFind == m_mediaType2backend.end()) {
524 : // xxx todo: more sophisticated media-type argument parsing...
525 0 : sal_Int32 q = mediaType.indexOf( ';' );
526 0 : if (q >= 0) {
527 : iFind = m_mediaType2backend.find(
528 : normalizeMediaType(
529 : // cut parameters:
530 0 : mediaType.copy( 0, q ) ) );
531 : }
532 : }
533 0 : if (iFind == m_mediaType2backend.end()) {
534 : throw lang::IllegalArgumentException(
535 0 : getResourceString(RID_STR_UNSUPPORTED_MEDIA_TYPE) + mediaType,
536 0 : static_cast<OWeakObject *>(this), static_cast<sal_Int16>(-1) );
537 : }
538 0 : return iFind->second->bindPackage( url, mediaType, bRemoved,
539 0 : identifier, xCmdEnv );
540 0 : }
541 : }
542 :
543 : //______________________________________________________________________________
544 : Sequence< Reference<deployment::XPackageTypeInfo> >
545 0 : PackageRegistryImpl::getSupportedPackageTypes() throw (RuntimeException)
546 : {
547 0 : return comphelper::containerToSequence(m_typesInfos);
548 : }
549 : } // anon namespace
550 :
551 : //==============================================================================
552 0 : Reference<deployment::XPackageRegistry> SAL_CALL create(
553 : OUString const & context,
554 : OUString const & cachePath, bool readOnly,
555 : Reference<XComponentContext> const & xComponentContext )
556 : {
557 : return PackageRegistryImpl::create(
558 0 : context, cachePath, readOnly, xComponentContext );
559 : }
560 :
561 : } // namespace dp_registry
562 :
563 : /* vim:set shiftwidth=4 softtabstop=4 expandtab: */
|