diff options
Diffstat (limited to 'dbaccess/source/ext/macromigration/migrationengine.cxx')
-rw-r--r-- | dbaccess/source/ext/macromigration/migrationengine.cxx | 1993 |
1 files changed, 1993 insertions, 0 deletions
diff --git a/dbaccess/source/ext/macromigration/migrationengine.cxx b/dbaccess/source/ext/macromigration/migrationengine.cxx new file mode 100644 index 000000000000..e85f60379514 --- /dev/null +++ b/dbaccess/source/ext/macromigration/migrationengine.cxx @@ -0,0 +1,1993 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/************************************************************************* + * + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * Copyright 2000, 2010 Oracle and/or its affiliates. + * + * OpenOffice.org - a multi-platform office productivity suite + * + * This file is part of OpenOffice.org. + * + * OpenOffice.org is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License version 3 + * only, as published by the Free Software Foundation. + * + * OpenOffice.org is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License version 3 for more details + * (a copy is included in the LICENSE file that accompanied this code). + * + * You should have received a copy of the GNU Lesser General Public License + * version 3 along with OpenOffice.org. If not, see + * <http://www.openoffice.org/license.html> + * for a copy of the LGPLv3 License. + * + ************************************************************************/ + +// MARKER(update_precomp.py): autogen include statement, do not remove +#include "precompiled_dbaccess.hxx" + +#include "dbmm_global.hrc" +#include "dbmm_module.hxx" +#include "dbmm_types.hxx" +#include "docinteraction.hxx" +#include "migrationengine.hxx" +#include "migrationerror.hxx" +#include "migrationprogress.hxx" +#include "migrationlog.hxx" +#include "progresscapture.hxx" +#include "progressmixer.hxx" + +/** === begin UNO includes === **/ +#include <com/sun/star/sdb/XFormDocumentsSupplier.hpp> +#include <com/sun/star/sdb/XReportDocumentsSupplier.hpp> +#include <com/sun/star/util/XCloseable.hpp> +#include <com/sun/star/frame/XModel.hpp> +#include <com/sun/star/frame/XComponentLoader.hpp> +#include <com/sun/star/ucb/XCommandProcessor.hpp> +#include <com/sun/star/ucb/XContent.hpp> +#include <com/sun/star/embed/XComponentSupplier.hpp> +#include <com/sun/star/embed/ElementModes.hpp> +#include <com/sun/star/document/XStorageBasedDocument.hpp> +#include <com/sun/star/embed/XTransactedObject.hpp> +#include <com/sun/star/frame/XStorable.hpp> +#include <com/sun/star/embed/XEmbedPersist.hpp> +#include <com/sun/star/script/DocumentScriptLibraryContainer.hpp> +#include <com/sun/star/script/DocumentDialogLibraryContainer.hpp> +#include <com/sun/star/document/XEmbeddedScripts.hpp> +#include <com/sun/star/document/XEventsSupplier.hpp> +#include <com/sun/star/uri/UriReferenceFactory.hpp> +#include <com/sun/star/uri/XVndSunStarScriptUrlReference.hpp> +#include <com/sun/star/form/XFormsSupplier.hpp> +#include <com/sun/star/drawing/XDrawPageSupplier.hpp> +#include <com/sun/star/drawing/XDrawPagesSupplier.hpp> +#include <com/sun/star/script/XEventAttacherManager.hpp> +#include <com/sun/star/script/XLibraryContainerPassword.hpp> +#include <com/sun/star/io/WrongFormatException.hpp> +#include <com/sun/star/script/XScriptEventsSupplier.hpp> +#include <com/sun/star/io/XInputStreamProvider.hpp> +/** === end UNO includes === **/ + +#include <comphelper/documentinfo.hxx> +#include <comphelper/interaction.hxx> +#include <comphelper/namedvaluecollection.hxx> +#include <comphelper/storagehelper.hxx> +#include <comphelper/string.hxx> +#include <comphelper/types.hxx> +#include <cppuhelper/exc_hlp.hxx> +#include <tools/string.hxx> +#include <tools/diagnose_ex.h> +#include <rtl/ustrbuf.hxx> +#include <rtl/ref.hxx> +#include <unotools/sharedunocomponent.hxx> +#include <xmlscript/xmldlg_imexp.hxx> + +#include <vector> +#include <set> + +#define DEFAULT_DOC_PROGRESS_RANGE 100000 + +//........................................................................ +namespace dbmm +{ +//........................................................................ + + /** === begin UNO using === **/ + using ::com::sun::star::uno::Reference; + using ::com::sun::star::uno::XInterface; + using ::com::sun::star::uno::UNO_QUERY; + using ::com::sun::star::uno::UNO_QUERY_THROW; + using ::com::sun::star::uno::UNO_SET_THROW; + using ::com::sun::star::uno::Exception; + using ::com::sun::star::uno::RuntimeException; + using ::com::sun::star::uno::Any; + using ::com::sun::star::uno::makeAny; + using ::com::sun::star::sdb::XOfficeDatabaseDocument; + using ::com::sun::star::sdb::XFormDocumentsSupplier; + using ::com::sun::star::sdb::XReportDocumentsSupplier; + using ::com::sun::star::container::XNameAccess; + using ::com::sun::star::uno::Sequence; + using ::com::sun::star::util::XCloseable; + using ::com::sun::star::util::CloseVetoException; + using ::com::sun::star::lang::XComponent; + using ::com::sun::star::frame::XModel; + using ::com::sun::star::frame::XComponentLoader; + using ::com::sun::star::ucb::XCommandProcessor; + using ::com::sun::star::ucb::XContent; + using ::com::sun::star::ucb::Command; + using ::com::sun::star::embed::XComponentSupplier; + using ::com::sun::star::task::XStatusIndicator; + using ::com::sun::star::embed::XStorage; + using ::com::sun::star::document::XStorageBasedDocument; + using ::com::sun::star::embed::XTransactedObject; + using ::com::sun::star::frame::XStorable; + using ::com::sun::star::embed::XEmbedPersist; + using ::com::sun::star::script::DocumentDialogLibraryContainer; + using ::com::sun::star::script::DocumentScriptLibraryContainer; + using ::com::sun::star::script::XStorageBasedLibraryContainer; + using ::com::sun::star::document::XEmbeddedScripts; + using ::com::sun::star::container::XNameContainer; + using ::com::sun::star::document::XEventsSupplier; + using ::com::sun::star::container::XNameReplace; + using com::sun::star::uri::UriReferenceFactory; + using com::sun::star::uri::XUriReferenceFactory; + using com::sun::star::uri::XVndSunStarScriptUrlReference; + using ::com::sun::star::form::XFormsSupplier; + using ::com::sun::star::drawing::XDrawPageSupplier; + using ::com::sun::star::drawing::XDrawPagesSupplier; + using ::com::sun::star::drawing::XDrawPage; + using ::com::sun::star::drawing::XDrawPages; + using ::com::sun::star::container::XIndexAccess; + using ::com::sun::star::script::XEventAttacherManager; + using ::com::sun::star::script::ScriptEventDescriptor; + using ::com::sun::star::script::XLibraryContainerPassword; + using ::com::sun::star::io::WrongFormatException; + using ::com::sun::star::script::XScriptEventsSupplier; + using ::com::sun::star::io::XInputStreamProvider; + using ::com::sun::star::io::XInputStream; + /** === end UNO using === **/ + namespace ElementModes = ::com::sun::star::embed::ElementModes; + +// migration phases whose progresses are to be mixed into one progress +#define PHASE_JAVASCRIPT 1 +#define PHASE_BEANSHELL 2 +#define PHASE_PYTHON 3 +#define PHASE_JAVA 4 +#define PHASE_BASIC 5 +#define PHASE_DIALOGS 6 + + //==================================================================== + //= SubDocument + //==================================================================== + struct SubDocument + { + Reference< XCommandProcessor > xCommandProcessor; + Reference< XModel > xDocument; // valid only temporarily + ::rtl::OUString sHierarchicalName; + SubDocumentType eType; + size_t nNumber; + + SubDocument( const Reference< XCommandProcessor >& _rxCommandProcessor, const ::rtl::OUString& _rName, + const SubDocumentType _eType, const size_t _nNumber ) + :xCommandProcessor( _rxCommandProcessor ) + ,xDocument() + ,sHierarchicalName( _rName ) + ,eType( _eType ) + ,nNumber( _nNumber ) + { + } + }; + + typedef ::std::vector< SubDocument > SubDocuments; + + //==================================================================== + //= helper + //==================================================================== + //-------------------------------------------------------------------- + typedef ::utl::SharedUNOComponent< XStorage > SharedStorage; + + namespace + { + //---------------------------------------------------------------- + static const ::rtl::OUString& lcl_getScriptsStorageName() + { + static const ::rtl::OUString s_sScriptsStorageName( RTL_CONSTASCII_USTRINGPARAM( "Scripts" ) ); + return s_sScriptsStorageName; + } + + //---------------------------------------------------------------- + static const ::rtl::OUString& lcl_getScriptsSubStorageName( const ScriptType _eType ) + { + static const ::rtl::OUString s_sBeanShell ( RTL_CONSTASCII_USTRINGPARAM( "beanshell" ) ); + static const ::rtl::OUString s_sJavaScript( RTL_CONSTASCII_USTRINGPARAM( "javascript" ) ); + static const ::rtl::OUString s_sPython ( RTL_CONSTASCII_USTRINGPARAM( "python" ) ); // TODO: is this correct? + static const ::rtl::OUString s_sJava ( RTL_CONSTASCII_USTRINGPARAM( "java" ) ); + + switch ( _eType ) + { + case eBeanShell: return s_sBeanShell; + case eJavaScript: return s_sJavaScript; + case ePython: return s_sPython; + case eJava: return s_sJava; + default: + break; + } + + OSL_FAIL( "lcl_getScriptsSubStorageName: illegal type!" ); + static ::rtl::OUString s_sEmpty; + return s_sEmpty; + } + + //---------------------------------------------------------------- + static bool lcl_getScriptTypeFromLanguage( const ::rtl::OUString& _rLanguage, ScriptType& _out_rScriptType ) + { + struct LanguageMapping + { + const sal_Char* pAsciiLanguage; + const ScriptType eScriptType; + + LanguageMapping( const sal_Char* _pAsciiLanguage, const ScriptType _eScriptType ) + :pAsciiLanguage( _pAsciiLanguage ) + ,eScriptType( _eScriptType ) + { + } + } + aLanguageMapping[] = + { + LanguageMapping( "JavaScript", eJavaScript ), + LanguageMapping( "BeanShell", eBeanShell ), + LanguageMapping( "Java", eJava ), + LanguageMapping( "Python", ePython ), // TODO: is this correct? + LanguageMapping( "Basic", eBasic ) + }; + for ( size_t i=0; i < SAL_N_ELEMENTS( aLanguageMapping ); ++i ) + { + if ( _rLanguage.equalsAscii( aLanguageMapping[i].pAsciiLanguage ) ) + { + _out_rScriptType = aLanguageMapping[i].eScriptType; + return true; + } + } + OSL_FAIL( "lcl_getScriptTypeFromLanguage: unknown language!" ); + return false; + } + + //---------------------------------------------------------------- + ::rtl::OUString lcl_getSubDocumentDescription( const SubDocument& _rDocument ) + { + ::rtl::OUString sObjectName = String( MacroMigrationResId( _rDocument.eType == eForm ? STR_FORM : STR_REPORT ) ); + ::comphelper::string::searchAndReplaceAsciiI( sObjectName, "$name$", _rDocument.sHierarchicalName ); + return sObjectName; + } + + //---------------------------------------------------------------- + static Any lcl_executeCommand_throw( const Reference< XCommandProcessor >& _rxCommandProc, + const sal_Char* _pAsciiCommand ) + { + OSL_PRECOND( _rxCommandProc.is(), "lcl_executeCommand_throw: illegal object!" ); + if ( !_rxCommandProc.is() ) + return Any(); + + Command aCommand; + aCommand.Name = ::rtl::OUString::createFromAscii( _pAsciiCommand ); + return _rxCommandProc->execute( + aCommand, _rxCommandProc->createCommandIdentifier(), NULL ); + } + + //---------------------------------------------------------------- + ::rtl::OUString lcl_getMimeType_nothrow( const Reference< XCommandProcessor >& _rxContent ) + { + ::rtl::OUString sMimeType; + try + { + Reference< XContent > xContent( _rxContent, UNO_QUERY_THROW ); + sMimeType = xContent->getContentType(); + } + catch( const Exception& ) + { + DBG_UNHANDLED_EXCEPTION(); + } + return sMimeType; + } + + //---------------------------------------------------------------- + enum OpenDocResult + { + eOpenedDoc, + eIgnoreDoc, + eFailure + }; + + //---------------------------------------------------------------- + static OpenDocResult lcl_loadSubDocument_nothrow( SubDocument& _rDocument, + const Reference< XStatusIndicator >& _rxProgress, MigrationLog& _rLogger ) + { + OSL_PRECOND( !_rDocument.xDocument.is(), "lcl_loadSubDocument_nothrow: already loaded!" ); + + try + { + ::comphelper::NamedValueCollection aLoadArgs; + aLoadArgs.put( "Hidden", (sal_Bool)sal_True ); + aLoadArgs.put( "StatusIndicator", _rxProgress ); + + Reference< XCommandProcessor > xCommandProcessor( _rDocument.xCommandProcessor, UNO_SET_THROW ); + Command aCommand; + aCommand.Name = ::rtl::OUString( RTL_CONSTASCII_USTRINGPARAM( "openDesign" ) ); + aCommand.Argument <<= aLoadArgs.getPropertyValues(); + Reference< XComponent > xDocComponent( + xCommandProcessor->execute( + aCommand, xCommandProcessor->createCommandIdentifier(), NULL + ), + UNO_QUERY + ); + OSL_ENSURE( xDocComponent.is(), "lcl_loadSubDocument_nothrow: no component loaded!" ); + + _rDocument.xDocument.set( xDocComponent, UNO_QUERY_THROW ); + } + catch( const Exception& ) + { + Any aError( ::cppu::getCaughtException() ); + + bool bCausedByNewStyleReport = + ( _rDocument.eType == eReport ) + && ( aError.isExtractableTo( ::cppu::UnoType< WrongFormatException >::get() ) ) + && ( lcl_getMimeType_nothrow( _rDocument.xCommandProcessor ).equalsAsciiL( RTL_CONSTASCII_STRINGPARAM( "application/vnd.sun.xml.report" ) ) ); + + if ( bCausedByNewStyleReport ) + { + _rLogger.logRecoverable( MigrationError( + ERR_NEW_STYLE_REPORT, + lcl_getSubDocumentDescription( _rDocument ) + ) ); + return eIgnoreDoc; + } + else + { + _rLogger.logFailure( MigrationError( + ERR_OPENING_SUB_DOCUMENT_FAILED, + lcl_getSubDocumentDescription( _rDocument ), + aError + ) ); + } + } + return _rDocument.xDocument.is() ? eOpenedDoc : eFailure; + } + + //---------------------------------------------------------------- + static bool lcl_unloadSubDocument_nothrow( SubDocument& _rDocument, MigrationLog& _rLogger ) + { + bool bSuccess = false; + Any aException; + try + { + OSL_VERIFY( lcl_executeCommand_throw( _rDocument.xCommandProcessor, "close" ) >>= bSuccess ); + } + catch( const Exception& ) + { + aException = ::cppu::getCaughtException(); + } + + // log the failure, if any + if ( !bSuccess ) + { + _rLogger.logFailure( MigrationError( + ERR_CLOSING_SUB_DOCUMENT_FAILED, + lcl_getSubDocumentDescription( _rDocument ), + aException + ) ); + } + + _rDocument.xDocument.clear(); + return bSuccess; + } + + //---------------------------------------------------------------- + bool lcl_commitStorage_nothrow( const Reference< XStorage >& _rxStorage ) + { + try + { + Reference< XTransactedObject > xTrans( _rxStorage, UNO_QUERY_THROW ); + xTrans->commit(); + } + catch( const Exception& ) + { + return false; + } + return true; + } + + //---------------------------------------------------------------- + bool lcl_commitDocumentStorage_nothrow( const Reference< XModel >& _rxDocument, MigrationLog& _rLogger ) + { + bool bSuccess = false; + Any aException; + try + { + Reference< XStorageBasedDocument > xStorageDoc( _rxDocument, UNO_QUERY_THROW ); + Reference< XStorage > xDocStorage( xStorageDoc->getDocumentStorage(), UNO_QUERY_THROW ); + bSuccess = lcl_commitStorage_nothrow( xDocStorage ); + } + catch( const Exception& ) + { + aException = ::cppu::getCaughtException(); + } + + // log the failure, if any + if ( !bSuccess ) + { + _rLogger.logFailure( MigrationError( + ERR_STORAGE_COMMIT_FAILED, + ::comphelper::DocumentInfo::getDocumentTitle( _rxDocument ), + aException + ) ); + } + return bSuccess; + } + + //---------------------------------------------------------------- + bool lcl_storeDocument_nothrow( const Reference< XModel >& _rxDocument, MigrationLog& _rLogger ) + { + bool bSuccess = false; + Any aException; + try + { + Reference< XStorable > xStorable( _rxDocument, UNO_QUERY_THROW ); + xStorable->store(); + bSuccess = true; + } + catch( const Exception& ) + { + aException = ::cppu::getCaughtException(); + } + + // log the failure, if any + if ( !bSuccess ) + { + _rLogger.logFailure( MigrationError( + ERR_STORING_DATABASEDOC_FAILED, + aException + ) ); + } + return bSuccess; + } + + //---------------------------------------------------------------- + bool lcl_storeEmbeddedDocument_nothrow( const SubDocument& _rDocument ) + { + try + { + lcl_executeCommand_throw( _rDocument.xCommandProcessor, "store" ); + } + catch( const Exception& ) + { + DBG_UNHANDLED_EXCEPTION(); + return false; + } + return true; + } + } + + //==================================================================== + //= DrawPageIterator + //==================================================================== + class DrawPageIterator + { + public: + DrawPageIterator( const Reference< XModel >& _rxDocument ) + :m_xDocument( _rxDocument ) + ,m_nPageCount( 0 ) + ,m_nCurrentPage( 0 ) + { + Reference< XDrawPageSupplier > xSingle( _rxDocument, UNO_QUERY ); + Reference< XDrawPagesSupplier > xMulti( _rxDocument, UNO_QUERY ); + if ( xSingle.is() ) + { + m_xSinglePage.set( xSingle->getDrawPage(), UNO_SET_THROW ); + m_nPageCount = 1; + } + else if ( xMulti.is() ) + { + m_xMultiPages.set( xMulti->getDrawPages(), UNO_SET_THROW ); + m_nPageCount = m_xMultiPages->getCount(); + } + } + + bool hasMore() const + { + return m_nCurrentPage < m_nPageCount; + } + + Reference< XDrawPage > next() + { + Reference< XDrawPage > xNextPage; + + if ( m_xSinglePage.is() ) + { + xNextPage = m_xSinglePage; + } + else if ( m_xMultiPages.is() ) + { + xNextPage.set( m_xMultiPages->getByIndex( m_nCurrentPage ), UNO_QUERY_THROW ); + } + ++m_nCurrentPage; + return xNextPage; + } + + private: + const Reference< XModel > m_xDocument; + Reference< XDrawPage > m_xSinglePage; + Reference< XDrawPages > m_xMultiPages; + sal_Int32 m_nPageCount; + sal_Int32 m_nCurrentPage; + }; + + //==================================================================== + //= FormComponentScripts + //==================================================================== + class FormComponentScripts + { + public: + FormComponentScripts( + const Reference< XInterface >& _rxComponent, + const Reference< XEventAttacherManager >& _rxManager, + const sal_Int32 _nIndex + ) + :m_xComponent( _rxComponent, UNO_SET_THROW ) + ,m_xManager( _rxManager, UNO_SET_THROW ) + ,m_nIndex( _nIndex ) + { + } + + Sequence< ScriptEventDescriptor > getEvents() const + { + return m_xManager->getScriptEvents( m_nIndex ); + } + + void setEvents( const Sequence< ScriptEventDescriptor >& _rEvents ) const + { + m_xManager->registerScriptEvents( m_nIndex, _rEvents ); + } + + const Reference< XInterface >& getComponent() const + { + return m_xComponent; + } + + private: + const Reference< XInterface > m_xComponent; + const Reference< XEventAttacherManager > m_xManager; + const sal_Int32 m_nIndex; + }; + + //==================================================================== + //= FormComponentIterator + //==================================================================== + class FormComponentIterator + { + public: + FormComponentIterator( const Reference< XIndexAccess >& _rxContainer ) + :m_xContainer( _rxContainer, UNO_SET_THROW ) + ,m_xEventManager( _rxContainer, UNO_QUERY_THROW ) + ,m_nElementCount( _rxContainer->getCount() ) + ,m_nCurrentElement( 0 ) + { + } + + bool hasMore() const + { + return m_nCurrentElement < m_nElementCount; + } + + FormComponentScripts next() + { + FormComponentScripts aComponent( + Reference< XInterface >( m_xContainer->getByIndex( m_nCurrentElement ), UNO_QUERY_THROW ), + m_xEventManager, + m_nCurrentElement + ); + ++m_nCurrentElement; + return aComponent; + } + + private: + const Reference< XIndexAccess > m_xContainer; + const Reference< XEventAttacherManager > m_xEventManager; + const sal_Int32 m_nElementCount; + sal_Int32 m_nCurrentElement; + + }; + + //==================================================================== + //= ScriptsStorage - declaration + //==================================================================== + /** a helper class which encapsulates access to the storages for Java/Script, BeanShell, and Python scripts, + i.e. all script types which can be manipulated on storage level. + */ + class ScriptsStorage + { + public: + ScriptsStorage( MigrationLog& _rLogger ); + ScriptsStorage( const Reference< XModel >& _rxDocument, MigrationLog& _rLogger ); + ~ScriptsStorage(); + + /** determines whether the instance is valid, i.e. refers to a valid root storage + for reading/storing scripts + */ + inline bool isValid() const { return m_xScriptsStorage.is(); } + + /** binds the instance to a new document. Only to be called when the instance is not yet + bound (i.e. isValid returns <FALSE/>). + */ + void bind( const Reference< XModel >& _rxDocument ); + + /// determines whether scripts of the given type are present + bool hasScripts( const ScriptType _eType ) const; + + /// returns the root storage for the scripts of the given type + SharedStorage + getScriptsRoot( const ScriptType _eType ) const; + + /** returns the names of the elements in the "Scripts" storage + */ + ::std::set< ::rtl::OUString > + getElementNames() const; + + /** removes the sub storage for a given script type + @precond + the respective storage is empty + @precond + the ScriptsStorage instance was opened for writing + */ + void removeScriptTypeStorage( const ScriptType _eType ) const; + + /** commits the changes at our XStorage object + */ + bool commit(); + + /** removes the "Scripts" sub storage from the given document's root storage + @precond + the "Scripts" storage is empty + */ + static bool + removeFromDocument( const Reference< XModel >& _rxDocument, MigrationLog& _rLogger ); + + private: + MigrationLog& m_rLogger; + SharedStorage m_xScriptsStorage; + }; + + //==================================================================== + //= ScriptsStorage - implementation + //==================================================================== + //-------------------------------------------------------------------- + ScriptsStorage::ScriptsStorage( MigrationLog& _rLogger ) + :m_rLogger( _rLogger ) + ,m_xScriptsStorage() + { + } + + //-------------------------------------------------------------------- + ScriptsStorage::ScriptsStorage( const Reference< XModel >& _rxDocument, MigrationLog& _rLogger ) + :m_rLogger( _rLogger ) + ,m_xScriptsStorage() + { + bind( _rxDocument ); + } + + //-------------------------------------------------------------------- + ScriptsStorage::~ScriptsStorage() + { + } + + //-------------------------------------------------------------------- + bool ScriptsStorage::commit() + { + return lcl_commitStorage_nothrow( m_xScriptsStorage ); + } + + //-------------------------------------------------------------------- + void ScriptsStorage::bind( const Reference< XModel >& _rxDocument ) + { + OSL_PRECOND( !isValid(), "ScriptsStorage:bind: did not bother, yet, to check whether this is allowed!" ); + try + { + Reference< XStorageBasedDocument > xStorageDoc( _rxDocument, UNO_QUERY_THROW ); + Reference< XStorage > xDocStorage( xStorageDoc->getDocumentStorage(), UNO_QUERY_THROW ); + + // the the "Scripts" storage exist, or if it does not (yet) exist and we are in write mode + // => open the storage + if ( ( xDocStorage->hasByName( lcl_getScriptsStorageName() ) + && xDocStorage->isStorageElement( lcl_getScriptsStorageName() ) + ) + || !xDocStorage->hasByName( lcl_getScriptsStorageName() ) + ) + { + m_xScriptsStorage.set( + xDocStorage->openStorageElement( + lcl_getScriptsStorageName(), ElementModes::READWRITE + ), + UNO_QUERY_THROW + ); + } + } + catch( const Exception& ) + { + m_rLogger.logFailure( MigrationError( + ERR_BIND_SCRIPT_STORAGE_FAILED, + ::comphelper::DocumentInfo::getDocumentTitle( _rxDocument ), + ::cppu::getCaughtException() + ) ); + } + } + + //-------------------------------------------------------------------- + bool ScriptsStorage::hasScripts( const ScriptType _eType ) const + { + OSL_PRECOND( isValid(), "ScriptsStorage::hasScripts: illegal call!" ); + if ( !isValid() ) + return false; + + const ::rtl::OUString& rSubStorageName( lcl_getScriptsSubStorageName( _eType ) ); + return m_xScriptsStorage->hasByName( rSubStorageName ) + && m_xScriptsStorage->isStorageElement( rSubStorageName ); + } + + //-------------------------------------------------------------------- + SharedStorage ScriptsStorage::getScriptsRoot( const ScriptType _eType ) const + { + SharedStorage xStorage; + if ( isValid() ) + { + xStorage.reset( m_xScriptsStorage->openStorageElement( + lcl_getScriptsSubStorageName( _eType ), ElementModes::READWRITE + ) ); + } + return xStorage; + } + + //-------------------------------------------------------------------- + ::std::set< ::rtl::OUString > ScriptsStorage::getElementNames() const + { + Sequence< ::rtl::OUString > aElementNames; + if ( isValid() ) + aElementNames = m_xScriptsStorage->getElementNames(); + + ::std::set< ::rtl::OUString > aNames; + ::std::copy( + aElementNames.getConstArray(), + aElementNames.getConstArray() + aElementNames.getLength(), + ::std::insert_iterator< ::std::set< ::rtl::OUString > >( aNames, aNames.end() ) + ); + return aNames; + } + + //-------------------------------------------------------------------- + void ScriptsStorage::removeScriptTypeStorage( const ScriptType _eType ) const + { + ::rtl::OUString sSubStorageName( lcl_getScriptsSubStorageName( _eType ) ); + if ( m_xScriptsStorage->hasByName( sSubStorageName ) ) + m_xScriptsStorage->removeElement( sSubStorageName ); + } + + //-------------------------------------------------------------------- + bool ScriptsStorage::removeFromDocument( const Reference< XModel >& _rxDocument, MigrationLog& _rLogger ) + { + try + { + Reference< XStorageBasedDocument > xStorageDoc( _rxDocument, UNO_QUERY_THROW ); + Reference< XStorage > xDocStorage( xStorageDoc->getDocumentStorage(), UNO_QUERY_THROW ); + xDocStorage->removeElement( lcl_getScriptsStorageName() ); + } + catch( const Exception& ) + { + _rLogger.logFailure( MigrationError( + ERR_REMOVE_SCRIPTS_STORAGE_FAILED, + ::comphelper::DocumentInfo::getDocumentTitle( _rxDocument ), + ::cppu::getCaughtException() + ) ) ; + return false; + } + return true; + } + + //==================================================================== + //= ProgressDelegator + //==================================================================== + class ProgressDelegator : public IProgressConsumer + { + public: + ProgressDelegator( IMigrationProgress& _rDelegator, + const ::rtl::OUString& _rObjectName, + const ::rtl::OUString& _rAction + ) + :m_rDelegator( _rDelegator ) + ,m_sObjectName( _rObjectName ) + ,m_sAction( _rAction ) + { + } + virtual ~ProgressDelegator() + { + } + + // IProgressConsumer + virtual void start( sal_uInt32 _nRange ) + { + m_rDelegator.startObject( m_sObjectName, m_sAction, _nRange ); + } + virtual void advance( sal_uInt32 _nValue ) + { + m_rDelegator.setObjectProgressValue( _nValue ); + } + virtual void end() + { + m_rDelegator.endObject(); + } + + private: + IMigrationProgress& m_rDelegator; + ::rtl::OUString m_sObjectName; + ::rtl::OUString m_sAction; + }; + + //==================================================================== + //= PhaseGuard + //==================================================================== + class PhaseGuard + { + public: + PhaseGuard( ProgressMixer& _rMixer ) + :m_rMixer( _rMixer ) + { + } + + PhaseGuard( ProgressMixer& _rMixer, const PhaseID _nID, const sal_uInt32 _nPhaseRange ) + :m_rMixer( _rMixer ) + { + start( _nID, _nPhaseRange ); + } + + ~PhaseGuard() + { + m_rMixer.endPhase(); + } + + void start( const PhaseID _nID, const sal_uInt32 _nPhaseRange ) + { + m_rMixer.startPhase( _nID, _nPhaseRange ); + } + + private: + ProgressMixer& m_rMixer; + }; + + //==================================================================== + //= MigrationEngine_Impl - declaration + //==================================================================== + class MigrationEngine_Impl + { + public: + MigrationEngine_Impl( + const ::comphelper::ComponentContext& _rContext, + const Reference< XOfficeDatabaseDocument >& _rxDocument, + IMigrationProgress& _rProgress, + MigrationLog& _rLogger + ); + ~MigrationEngine_Impl(); + + inline size_t getFormCount() const { return m_nFormCount; } + inline size_t getReportCount()const { return m_nReportCount; } + bool migrateAll(); + + private: + ::comphelper::ComponentContext m_aContext; + const Reference< XOfficeDatabaseDocument > m_xDocument; + const Reference< XModel > m_xDocumentModel; + IMigrationProgress& m_rProgress; + MigrationLog& m_rLogger; + mutable DocumentID m_nCurrentDocumentID; + SubDocuments m_aSubDocs; + size_t m_nFormCount; + size_t m_nReportCount; + + private: + /** collects a description of all sub documents of our database document + + @return + <TRUE/> if and only if collecting the documents was successful + */ + bool impl_collectSubDocuments_nothrow(); + + /** migrates the macros/scripts of the given sub document + */ + bool impl_handleDocument_nothrow( const SubDocument& _rDocument ) const; + + /** checks the structure of the 'Scripts' folder of a sub document + for unknown elements + + @return + <TRUE/> if and only if the 'Scripts' folder contains known elements only. + */ + bool impl_checkScriptStorageStructure_nothrow( const SubDocument& _rDocument ) const; + + /** migrates the scripts of the given "storage-based" script type + */ + bool impl_migrateScriptStorage_nothrow( + const SubDocument& _rDocument, + const ScriptType _eScriptType, + ProgressMixer& _rProgress, + const PhaseID _nPhaseID + ) const; + + /** migrates the content of the given "container based" libraries (Basic/Dialogs) + */ + bool impl_migrateContainerLibraries_nothrow( + const SubDocument& _rDocument, + const ScriptType _eScriptType, + ProgressMixer& _rProgress, + const PhaseID _nPhaseID + ) const; + + /** adjusts the events for the given dialog/element, taking into account the new names + of the moved libraries + */ + void impl_adjustDialogElementEvents_throw( + const Reference< XInterface >& _rxElement + ) const; + + /** adjusts the events in the given dialog, and its controls, taking into account the new names + of the moved libraries + */ + bool impl_adjustDialogEvents_nothrow( + Any& _inout_rDialogLibraryElement, + const ::rtl::OUString& _rDocName, + const ::rtl::OUString& _rDialogLibName, + const ::rtl::OUString& _rDialogName + ) const; + + /** adjust the document-events which refer to macros/scripts in the document, taking into + account the new names of the moved libraries + */ + bool impl_adjustDocumentEvents_nothrow( + const SubDocument& _rDocument + ) const; + + /** adjusts the script references bound to form component events + */ + bool impl_adjustFormComponentEvents_nothrow( + const SubDocument& _rDocument + ) const; + + /** adjusts the script references for the elements of the given form component container + */ + void impl_adjustFormComponentEvents_throw( + const Reference< XIndexAccess >& _rxComponentContainer + ) const; + + /** adjusts the library name in the given script URL, so that it reflects + the new name of the library + + @return <TRUE/> + if and only if adjustments to the script code have been made + */ + bool impl_adjustScriptLibrary_nothrow( + const ::rtl::OUString& _rScriptType, + ::rtl::OUString& _inout_rScriptCode + ) const; + + bool impl_adjustScriptLibrary_nothrow( Any& _inout_rScriptDescriptor ) const; + bool impl_adjustScriptLibrary_nothrow( ScriptEventDescriptor& _inout_rScriptEvent ) const; + + /** asks the user for a password for the given library, and unprotects the library + + @return <TRUE/> + if and only if the library could be successfully unprotected + */ + bool impl_unprotectPasswordLibrary_throw( + const Reference< XLibraryContainerPassword >& _rxPasswordManager, + const ScriptType _eScriptType, + const ::rtl::OUString& _rLibraryName + ) const; + }; + + //==================================================================== + //= MigrationEngine_Impl - implementation + //==================================================================== + //-------------------------------------------------------------------- + MigrationEngine_Impl::MigrationEngine_Impl( const ::comphelper::ComponentContext& _rContext, + const Reference< XOfficeDatabaseDocument >& _rxDocument, IMigrationProgress& _rProgress, MigrationLog& _rLogger ) + :m_aContext( _rContext ) + ,m_xDocument( _rxDocument ) + ,m_xDocumentModel( _rxDocument, UNO_QUERY_THROW ) + ,m_rProgress( _rProgress ) + ,m_rLogger( _rLogger ) + ,m_nCurrentDocumentID( - 1 ) + ,m_aSubDocs() + ,m_nFormCount( 0 ) + ,m_nReportCount( 0 ) + { + OSL_VERIFY( impl_collectSubDocuments_nothrow() ); + } + + //-------------------------------------------------------------------- + MigrationEngine_Impl::~MigrationEngine_Impl() + { + } + + //-------------------------------------------------------------------- + bool MigrationEngine_Impl::migrateAll() + { + if ( m_aSubDocs.empty() ) + { + OSL_FAIL( "MigrationEngine_Impl::migrateAll: no forms/reports found!" ); + // The whole migration wizard is not expected to be called when there are no forms/reports + // with macros, not to mention when there are no forms/reports at all. + return false; + } + + // initialize global progress + sal_Int32 nOverallRange( m_aSubDocs.size() ); + String sProgressSkeleton = String( MacroMigrationResId( STR_OVERALL_PROGRESS ) ); + sProgressSkeleton.SearchAndReplaceAscii( "$overall$", String::CreateFromInt32( nOverallRange ) ); + + m_rProgress.start( nOverallRange ); + + for ( SubDocuments::const_iterator doc = m_aSubDocs.begin(); + doc != m_aSubDocs.end(); + ++doc + ) + { + sal_Int32 nOverallProgressValue( doc - m_aSubDocs.begin() + 1 ); + // update overall progress text + ::rtl::OUString sOverallProgress( sProgressSkeleton ); + ::comphelper::string::searchAndReplaceAsciiI( sOverallProgress, "$current$", ::rtl::OUString::valueOf( nOverallProgressValue ) ); + m_rProgress.setOverallProgressText( sOverallProgress ); + + // migrate document + if ( !impl_handleDocument_nothrow( *doc ) ) + return false; + + // update overall progress vallue + m_rProgress.setOverallProgressValue( nOverallProgressValue ); + } + + // commit the root storage of the database document, for all changes made so far to take effect + if ( !lcl_commitDocumentStorage_nothrow( m_xDocumentModel, m_rLogger ) ) + return false; + + // save the document + if ( !lcl_storeDocument_nothrow( m_xDocumentModel, m_rLogger ) ) + return false; + + return true; + } + + //-------------------------------------------------------------------- + namespace + { + void lcl_collectHierarchicalElementNames_throw( + const Reference< XNameAccess >& _rxContainer, const ::rtl::OUString& _rContainerLoc, + SubDocuments& _out_rDocs, const SubDocumentType _eType, size_t& _io_counter ) + { + const ::rtl::OUString sHierarhicalBase( + _rContainerLoc.getLength() ? ::rtl::OUStringBuffer( _rContainerLoc ).appendAscii( "/" ).makeStringAndClear() + : ::rtl::OUString() ); + + Sequence< ::rtl::OUString > aElementNames( _rxContainer->getElementNames() ); + for ( const ::rtl::OUString* elementName = aElementNames.getConstArray(); + elementName != aElementNames.getConstArray() + aElementNames.getLength(); + ++elementName + ) + { + Any aElement( _rxContainer->getByName( *elementName ) ); + ::rtl::OUString sElementName( ::rtl::OUStringBuffer( sHierarhicalBase ).append( *elementName ) ); + + Reference< XNameAccess > xSubContainer( aElement, UNO_QUERY ); + if ( xSubContainer.is() ) + { + lcl_collectHierarchicalElementNames_throw( xSubContainer, sElementName, _out_rDocs, _eType, _io_counter ); + } + else + { + Reference< XCommandProcessor > xCommandProcessor( aElement, UNO_QUERY ); + OSL_ENSURE( xCommandProcessor.is(), "lcl_collectHierarchicalElementNames_throw: no container, and no comand processor? What *is* it, then?!" ); + if ( xCommandProcessor.is() ) + { + _out_rDocs.push_back( SubDocument( xCommandProcessor, sElementName, _eType, ++_io_counter ) ); + } + } + } + } + } + + //-------------------------------------------------------------------- + bool MigrationEngine_Impl::impl_collectSubDocuments_nothrow() + { + OSL_PRECOND( m_xDocument.is(), "MigrationEngine_Impl::impl_collectSubDocuments_nothrow: invalid document!" ); + if ( !m_xDocument.is() ) + return false; + + try + { + Reference< XNameAccess > xDocContainer( m_xDocument->getFormDocuments(), UNO_SET_THROW ); + m_nFormCount = 0; + lcl_collectHierarchicalElementNames_throw( xDocContainer, ::rtl::OUString(), m_aSubDocs, eForm, m_nFormCount ); + + xDocContainer.set( m_xDocument->getReportDocuments(), UNO_SET_THROW ); + m_nReportCount = 0; + lcl_collectHierarchicalElementNames_throw( xDocContainer, ::rtl::OUString(), m_aSubDocs, eReport, m_nReportCount ); + } + catch( const Exception& ) + { + m_rLogger.logFailure( MigrationError( + ERR_COLLECTING_DOCUMENTS_FAILED, + ::cppu::getCaughtException() + ) ); + return false; + } + return true; + } + + //-------------------------------------------------------------------- + bool MigrationEngine_Impl::impl_handleDocument_nothrow( const SubDocument& _rDocument ) const + { + OSL_ENSURE( m_nCurrentDocumentID == -1, + "MigrationEngine_Impl::impl_handleDocument_nothrow: there already is a current document!"); + m_nCurrentDocumentID = m_rLogger.startedDocument( _rDocument.eType, _rDocument.sHierarchicalName ); + + // start the progress + ::rtl::OUString sObjectName( lcl_getSubDocumentDescription( _rDocument ) ); + m_rProgress.startObject( sObjectName, ::rtl::OUString(), DEFAULT_DOC_PROGRESS_RANGE ); + + // ----------------- + // load the document + ::rtl::Reference< ProgressCapture > pStatusIndicator( new ProgressCapture( sObjectName, m_rProgress ) ); + SubDocument aSubDocument( _rDocument ); + OpenDocResult eResult = lcl_loadSubDocument_nothrow( aSubDocument, pStatusIndicator.get(), m_rLogger ); + if ( eResult != eOpenedDoc ) + { + pStatusIndicator->dispose(); + m_rProgress.endObject(); + m_rLogger.finishedDocument( m_nCurrentDocumentID ); + m_nCurrentDocumentID = -1; + return ( eResult == eIgnoreDoc ); + } + + // ----------------- + // migrate the libraries + ProgressDelegator aDelegator( m_rProgress, sObjectName, String( MacroMigrationResId( STR_MIGRATING_LIBS ) ) ); + ProgressMixer aProgressMixer( aDelegator ); + aProgressMixer.registerPhase( PHASE_JAVASCRIPT, 1 ); + aProgressMixer.registerPhase( PHASE_BEANSHELL, 1 ); + aProgressMixer.registerPhase( PHASE_PYTHON, 1 ); + aProgressMixer.registerPhase( PHASE_JAVA, 1 ); + aProgressMixer.registerPhase( PHASE_BASIC, 5 ); + // more weight than the others, assuming that usually, there are many more Basic macros than any other scripts + aProgressMixer.registerPhase( PHASE_DIALOGS, 1 ); + + bool bSuccess = impl_checkScriptStorageStructure_nothrow( aSubDocument ); + + // migrate storage-based script libraries (which can be handled by mere storage operations) + bSuccess = bSuccess + && impl_migrateScriptStorage_nothrow( aSubDocument, eJavaScript, aProgressMixer, PHASE_JAVASCRIPT ) + && impl_migrateScriptStorage_nothrow( aSubDocument, eBeanShell, aProgressMixer, PHASE_BEANSHELL ) + && impl_migrateScriptStorage_nothrow( aSubDocument, ePython, aProgressMixer, PHASE_PYTHON ) + && impl_migrateScriptStorage_nothrow( aSubDocument, eJava, aProgressMixer, PHASE_JAVA ); + + // migrate Basic and dialog libraries + bSuccess = bSuccess + && impl_migrateContainerLibraries_nothrow( aSubDocument, eBasic, aProgressMixer, PHASE_BASIC ) + && impl_migrateContainerLibraries_nothrow( aSubDocument, eDialog, aProgressMixer, PHASE_DIALOGS ); + // order matters: First Basic scripts, then dialogs. So we can adjust references from the latter + // to the former + + // adjust the events in the document + // (note that errors are ignored here - failure to convert a script reference + // is not considered a critical error) + if ( bSuccess ) + { + impl_adjustDocumentEvents_nothrow( aSubDocument ); + impl_adjustFormComponentEvents_nothrow( aSubDocument ); + } + + // ----------------- + // clean up + // store the sub document, including removal of the (now obsolete) "Scripts" sub folder + if ( m_rLogger.movedAnyLibrary( m_nCurrentDocumentID ) ) + { + bSuccess = bSuccess + && ScriptsStorage::removeFromDocument( aSubDocument.xDocument, m_rLogger ) + && lcl_commitDocumentStorage_nothrow( aSubDocument.xDocument, m_rLogger ) + && lcl_storeEmbeddedDocument_nothrow( aSubDocument ); + } + + // unload in any case, even if we were not successful + bSuccess = lcl_unloadSubDocument_nothrow( aSubDocument, m_rLogger ) + && bSuccess; + + pStatusIndicator->dispose(); + + // end the progress, just in case the ProgressCapture didn't receive the XStatusIndicator::end event + m_rProgress.endObject(); + + m_rLogger.finishedDocument( m_nCurrentDocumentID ); + m_nCurrentDocumentID = -1; + return bSuccess; + } + + //-------------------------------------------------------------------- + namespace + { + static ::rtl::OUString lcl_createTargetLibName( const SubDocument& _rDocument, + const ::rtl::OUString& _rSourceLibName, const Reference< XNameAccess >& _rxTargetContainer ) + { + // The new library name is composed from the prefix, the base name, and the old library name. + const ::rtl::OUString sPrefix = (_rDocument.eType == eForm)?rtl::OUString(RTL_CONSTASCII_USTRINGPARAM("Form_")): rtl::OUString(RTL_CONSTASCII_USTRINGPARAM("Report_")); + + ::rtl::OUString sBaseName( _rDocument.sHierarchicalName.copy( + _rDocument.sHierarchicalName.lastIndexOf( '/' ) + 1 ) ); + // Normalize this name. In our current storage implementation (and script containers in a document + // are finally mapped to sub storages of the document storage), not all characters are allowed. + // The bug requesting to change this is #i95409#. + // Unfortunately, the storage implementation does not complain if you use invalid characters/names, but instead + // it silently accepts them, and produces garbage in the file (#i95408). + // So, until especially the former is fixed, we need to strip all invalid characters from the name. + // #i95865# + + // The general idea is to replace invalid characters with '_'. However, since "valid" essentially means + // ASCII only, this implies that for a lot of languages, we would simply replace everything with '_', + // which of course is not desired. + // So, we use a heuristics: If the name contains at most 3 invalid characters, and as many valid as invalid + // characters, then we use the replacement. Otherwise, we just use a unambiguous number for the sub document. + sal_Int32 nValid=0, nInvalid=0; + const sal_Unicode* pBaseName = sBaseName.getStr(); + const sal_Int32 nBaseNameLen = sBaseName.getLength(); + for ( sal_Int32 i=0; i<nBaseNameLen; ++i ) + { + if ( ::comphelper::OStorageHelper::IsValidZipEntryFileName( pBaseName + i, 1, sal_False ) ) + ++nValid; + else + ++nInvalid; + } + if ( ( nInvalid <= 3 ) && ( nInvalid * 2 <= nValid ) ) + { // not "too many" invalid => replace them + ::rtl::OUStringBuffer aReplacement; + aReplacement.ensureCapacity( nBaseNameLen ); + aReplacement.append( sBaseName ); + const sal_Unicode* pReplacement = aReplacement.getStr(); + for ( sal_Int32 i=0; i<nBaseNameLen; ++i ) + { + if ( !::comphelper::OStorageHelper::IsValidZipEntryFileName( pReplacement + i, 1, sal_False ) ) + aReplacement.setCharAt( i, '_' ); + } + sBaseName = aReplacement.makeStringAndClear(); + + ::rtl::OUStringBuffer aNewLibNameAttempt; + aNewLibNameAttempt.append( sPrefix ); + aNewLibNameAttempt.append( sBaseName ); + aNewLibNameAttempt.appendAscii( "_" ); + aNewLibNameAttempt.append( _rSourceLibName ); + ::rtl::OUString sTargetName( aNewLibNameAttempt.makeStringAndClear() ); + if ( !_rxTargetContainer->hasByName( sTargetName ) ) + return sTargetName; + } + + // "too many" invalid characters, or the name composed with the base name was already used. + // (The latter is valid, since there can be multiple sub documents with the same base name, + // in different levels in the hierarchy.) + // In this case, just use the umambiguous sub document number. + ::rtl::OUStringBuffer aNewLibName; + aNewLibName.append( sPrefix ); + aNewLibName.append( ::rtl::OUString::valueOf( sal_Int64( _rDocument.nNumber ) ) ); + aNewLibName.appendAscii( "_" ); + aNewLibName.append( _rSourceLibName ); + return aNewLibName.makeStringAndClear(); + } + } + + //-------------------------------------------------------------------- + bool MigrationEngine_Impl::impl_checkScriptStorageStructure_nothrow( const SubDocument& _rDocument ) const + { + OSL_PRECOND( _rDocument.xDocument.is(), "MigrationEngine_Impl::impl_checkScriptStorageStructure_nothrow: invalid document!" ); + if ( !_rDocument.xDocument.is() ) + return false; + + try + { + // the root storage of the document whose scripts are to be migrated + ScriptsStorage aDocStorage( _rDocument.xDocument, m_rLogger ); + if ( !aDocStorage.isValid() ) + { // no scripts at all, or no scripts of the given type + return !m_rLogger.hadFailure(); + } + ::std::set< ::rtl::OUString > aElementNames( aDocStorage.getElementNames() ); + + ScriptType aKnownStorageBasedTypes[] = { + eBeanShell, eJavaScript, ePython, eJava + }; + for ( size_t i=0; i< SAL_N_ELEMENTS( aKnownStorageBasedTypes ); ++i ) + aElementNames.erase( lcl_getScriptsSubStorageName( aKnownStorageBasedTypes[i] ) ); + + if ( !aElementNames.empty() ) + { + m_rLogger.logFailure( MigrationError( + ERR_UNKNOWN_SCRIPT_FOLDER, + lcl_getSubDocumentDescription( _rDocument ), + *aElementNames.begin() + ) ); + return false; + } + } + catch( const Exception& ) + { + m_rLogger.logFailure( MigrationError( + ERR_EXAMINING_SCRIPTS_FOLDER_FAILED, + lcl_getSubDocumentDescription( _rDocument ), + ::cppu::getCaughtException() + ) ); + return false; + } + return true; + } + + //-------------------------------------------------------------------- + bool MigrationEngine_Impl::impl_migrateScriptStorage_nothrow( const SubDocument& _rDocument, + const ScriptType _eScriptType, ProgressMixer& _rProgress, const PhaseID _nPhaseID ) const + { + OSL_PRECOND( _rDocument.xDocument.is(), "MigrationEngine_Impl::impl_migrateScriptStorage_nothrow: invalid document!" ); + if ( !_rDocument.xDocument.is() ) + return false; + + ScriptsStorage aDatabaseScripts( m_rLogger ); + // the scripts of our complete database document - created on demand only + SharedStorage xTargetStorage; + // the target for moving the scripts storages - created on demand only + + PhaseGuard aPhase( _rProgress ); + bool bSuccess = false; + Any aException; + try + { + // the root storage of the document whose scripts are to be migrated + ScriptsStorage aDocStorage( _rDocument.xDocument, m_rLogger ); + if ( !aDocStorage.isValid() + || !aDocStorage.hasScripts( _eScriptType ) + ) + { + // no scripts at all, or no scripts of the given type + _rProgress.startPhase( _nPhaseID, 1 ); + _rProgress.endPhase(); + return !m_rLogger.hadFailure(); + } + + SharedStorage xScriptsRoot( aDocStorage.getScriptsRoot( _eScriptType ) ); + if ( !xScriptsRoot.is() ) + throw RuntimeException( ::rtl::OUString( RTL_CONSTASCII_USTRINGPARAM( "internal error" ) ), NULL ); + + // loop through the script libraries + Sequence< ::rtl::OUString > aStorageElements( xScriptsRoot->getElementNames() ); + aPhase.start( _nPhaseID, aStorageElements.getLength() ); + + for ( const ::rtl::OUString* element = aStorageElements.getConstArray(); + element != aStorageElements.getConstArray() + aStorageElements.getLength(); + ++element + ) + { + bool bIsScriptLibrary = xScriptsRoot->isStorageElement( *element ); + OSL_ENSURE( bIsScriptLibrary, + "MigrationEngine_Impl::impl_migrateScriptStorage_nothrow: warning: unknown scripts storage structure!" ); + // we cannot handle this. We would need to copy this stream to the respective scripts storage + // of the database document, but we cannot guarantee that the name is not used, yet, and we cannot + // simply rename the thing. + if ( !bIsScriptLibrary ) + { + m_rLogger.logFailure( MigrationError( + ERR_UNEXPECTED_LIBSTORAGE_ELEMENT, + lcl_getSubDocumentDescription( _rDocument ), + getScriptTypeDisplayName( _eScriptType ), + *element + ) ); + return false; + } + + // ensure we have access to the DBDoc's scripts storage + if ( !aDatabaseScripts.isValid() ) + { // not needed 'til now + aDatabaseScripts.bind( m_xDocumentModel ); + if ( aDatabaseScripts.isValid() ) + xTargetStorage = aDatabaseScripts.getScriptsRoot( _eScriptType ); + + if ( !xTargetStorage.is() ) + { + m_rLogger.logFailure( MigrationError( + ERR_CREATING_DBDOC_SCRIPT_STORAGE_FAILED, + getScriptTypeDisplayName( _eScriptType ) + ) ); + return false; + } + } + + // move the library to the DBDoc's scripts library, under the new name + ::rtl::OUString sNewLibName( lcl_createTargetLibName( _rDocument, *element, xTargetStorage.getTyped().get() ) ); + xScriptsRoot->moveElementTo( *element, xTargetStorage, sNewLibName ); + + // log the fact that we moved the library + m_rLogger.movedLibrary( m_nCurrentDocumentID, _eScriptType, *element, sNewLibName ); + + // progress + _rProgress.advancePhase( element - aStorageElements.getConstArray() ); + } + + // commit the storages, so the changes we made persist + if ( !lcl_commitStorage_nothrow( xScriptsRoot ) + || ( xTargetStorage.is() && !lcl_commitStorage_nothrow( xTargetStorage ) ) + ) + { + m_rLogger.logFailure( MigrationError( + ERR_COMMITTING_SCRIPT_STORAGES_FAILED, + getScriptTypeDisplayName( _eScriptType ), + lcl_getSubDocumentDescription( _rDocument ) + ) ); + return false; + } + + // now that the concrete scripts storage does not have any elements anymore, + // remove it + xScriptsRoot.reset( NULL ); // need to reset the storage to be allowed to remove it + aDocStorage.removeScriptTypeStorage( _eScriptType ); + + // done so far + bSuccess = aDocStorage.commit() + && aDatabaseScripts.commit(); + } + catch( const Exception& ) + { + aException = ::cppu::getCaughtException(); + bSuccess = false; + } + + // log the error, if any + if ( !bSuccess ) + { + m_rLogger.logFailure( MigrationError( + ERR_GENERAL_SCRIPT_MIGRATION_FAILURE, + getScriptTypeDisplayName( _eScriptType ), + lcl_getSubDocumentDescription( _rDocument ), + aException + ) ); + } + + return bSuccess; + } + + //-------------------------------------------------------------------- + bool MigrationEngine_Impl::impl_migrateContainerLibraries_nothrow( const SubDocument& _rDocument, + const ScriptType _eScriptType, ProgressMixer& _rProgress, const PhaseID _nPhaseID ) const + { + OSL_PRECOND( ( _eScriptType == eBasic ) || ( _eScriptType == eDialog ), + "MigrationEngine_Impl::impl_migrateContainerLibraries_nothrow: illegal script type!" ); + + bool bSuccess = false; + PhaseGuard aPhase( _rProgress ); + Any aException; + do // artificial loop for flow control only + { + try + { + // access library container of the sub document + Reference< XEmbeddedScripts > xSubDocScripts( _rDocument.xDocument, UNO_QUERY ); + if ( !xSubDocScripts.is() ) + { // no script support in the sub document -> nothing to migrate + // (though ... this is suspicious, at least ...) + bSuccess = true; + break; + } + + Reference< XStorageBasedLibraryContainer > xSourceLibraries( + _eScriptType == eBasic ? xSubDocScripts->getBasicLibraries() : xSubDocScripts->getDialogLibraries(), + UNO_QUERY_THROW + ); + Reference< XLibraryContainerPassword > xSourcePasswords( xSourceLibraries, UNO_QUERY ); + OSL_ENSURE( xSourcePasswords.is(), + "MigrationEngine_Impl::impl_migrateContainerLibraries_nothrow: suspicious: no password management for the source libraries!" ); + + Sequence< ::rtl::OUString > aSourceLibNames( xSourceLibraries->getElementNames() ); + aPhase.start( _nPhaseID, aSourceLibNames.getLength() ); + + if ( !xSourceLibraries->hasElements() ) + { + bSuccess = true; + break; + } + + // create library containers for the document - those will be the target for the migration + Reference< XStorageBasedDocument > xStorageDoc( m_xDocument, UNO_QUERY_THROW ); + Reference< XStorageBasedLibraryContainer > xTargetLibraries; + if ( _eScriptType == eBasic ) + { + xTargetLibraries.set( DocumentScriptLibraryContainer::create( + m_aContext.getUNOContext(), xStorageDoc ), UNO_QUERY_THROW ); + } + else + { + xTargetLibraries.set( DocumentDialogLibraryContainer::create( + m_aContext.getUNOContext(), xStorageDoc ), UNO_QUERY_THROW ); + } + + // copy all libs to the target, with potentially renaming them + const ::rtl::OUString* pSourceLibBegin = aSourceLibNames.getConstArray(); + const ::rtl::OUString* pSourceLibEnd = pSourceLibBegin + aSourceLibNames.getLength(); + for ( const ::rtl::OUString* pSourceLibName = pSourceLibBegin; + pSourceLibName != pSourceLibEnd; + ++pSourceLibName + ) + { + // if the library is password-protected, ask the user to unprotect it + if ( xSourcePasswords.is() + && xSourcePasswords->isLibraryPasswordProtected( *pSourceLibName ) + && !xSourcePasswords->isLibraryPasswordVerified( *pSourceLibName ) + ) + { + if ( !impl_unprotectPasswordLibrary_throw( xSourcePasswords, _eScriptType, *pSourceLibName ) ) + { + m_rLogger.logFailure( MigrationError( + ERR_PASSWORD_VERIFICATION_FAILED, + _rDocument.sHierarchicalName, + getScriptTypeDisplayName( _eScriptType ), + *pSourceLibName + ) ); + return false; + } + } + + ::rtl::OUString sNewLibName( lcl_createTargetLibName( _rDocument, *pSourceLibName, xTargetLibraries.get() ) ); + + if ( xSourceLibraries->isLibraryLink( *pSourceLibName ) ) + { + // just re-create the link in the target library + xTargetLibraries->createLibraryLink( + sNewLibName, + xSourceLibraries->getLibraryLinkURL( *pSourceLibName ), + xSourceLibraries->isLibraryReadOnly( *pSourceLibName ) + ); + } + else + { + if ( !xSourceLibraries->isLibraryLoaded( *pSourceLibName ) ) + xSourceLibraries->loadLibrary( *pSourceLibName ); + + // copy the content of this particular libary + Reference< XNameAccess > xSourceLib( xSourceLibraries->getByName( *pSourceLibName ), UNO_QUERY_THROW ); + Reference< XNameContainer > xTargetLib( xTargetLibraries->createLibrary( sNewLibName ), UNO_QUERY_THROW ); + + Sequence< ::rtl::OUString > aLibElementNames( xSourceLib->getElementNames() ); + for ( const ::rtl::OUString* pSourceElementName = aLibElementNames.getConstArray(); + pSourceElementName != aLibElementNames.getConstArray() + aLibElementNames.getLength(); + ++pSourceElementName + ) + { + Any aElement = xSourceLib->getByName( *pSourceElementName ); + OSL_ENSURE( aElement.hasValue(), + "MigrationEngine_Impl::impl_migrateContainerLibraries_nothrow: invalid (empty) lib element!" ); + + // if this is a dialog, adjust the references to scripts + if ( _eScriptType == eDialog ) + { + impl_adjustDialogEvents_nothrow( aElement, lcl_getSubDocumentDescription( _rDocument ), + *pSourceLibName, *pSourceElementName ); + } + + xTargetLib->insertByName( *pSourceElementName, aElement ); + } + + // transfer the read-only flag + xTargetLibraries->setLibraryReadOnly( + sNewLibName, xSourceLibraries->isLibraryReadOnly( *pSourceLibName ) ); + } + + // remove the source lib + xSourceLibraries->removeLibrary( *pSourceLibName ); + + // tell the logger + m_rLogger.movedLibrary( m_nCurrentDocumentID, _eScriptType, *pSourceLibName, sNewLibName ); + + // tell the progress + _rProgress.advancePhase( pSourceLibName - pSourceLibBegin ); + } + + // clean up + xSourceLibraries->storeLibraries(); + + xTargetLibraries->storeLibraries(); + Reference< XStorage > xTargetRoot( xTargetLibraries->getRootLocation(), UNO_QUERY_THROW ); + bSuccess = lcl_commitStorage_nothrow( xTargetRoot ); + } + catch( const Exception& ) + { + aException = ::cppu::getCaughtException(); + bSuccess = false; + } + } while ( false ); + + // log the error, if any + if ( !bSuccess ) + { + m_rLogger.logFailure( MigrationError( + ERR_GENERAL_MACRO_MIGRATION_FAILURE, + lcl_getSubDocumentDescription( _rDocument ), + aException + ) ); + } + + return bSuccess; + } + + //-------------------------------------------------------------------- + bool MigrationEngine_Impl::impl_adjustScriptLibrary_nothrow( const ::rtl::OUString& _rScriptType, + ::rtl::OUString& _inout_rScriptCode ) const + { + OSL_PRECOND( _inout_rScriptCode.getLength(), "MigrationEngine_Impl::impl_adjustScriptLibrary_nothrow: invalid script!" ); + if ( !_inout_rScriptCode.getLength() ) + return false; + + bool bSuccess = false; + Any aException; + try + { + if ( !_rScriptType.equalsAsciiL( RTL_CONSTASCII_STRINGPARAM( "Script" ) ) + || !_rScriptType.getLength() + ) + { + OSL_FAIL( + "MigrationEngine_Impl::impl_adjustScriptLibrary_nothrow: no or unknown script type!" ); + m_rLogger.logRecoverable( MigrationError( + ERR_UNKNOWN_SCRIPT_TYPE, + _rScriptType + ) ); + return false; + } + + // analyze the script URI + Reference< XUriReferenceFactory > xUriRefFac = UriReferenceFactory::create( m_aContext.getUNOContext() ); + Reference< XVndSunStarScriptUrlReference > xUri( xUriRefFac->parse( _inout_rScriptCode ), UNO_QUERY_THROW ); + + ::rtl::OUString sScriptLanguage = xUri->getParameter( + ::rtl::OUString( RTL_CONSTASCII_USTRINGPARAM( "language" ) ) ); + ScriptType eScriptType = eBasic; + if ( !lcl_getScriptTypeFromLanguage( sScriptLanguage, eScriptType ) ) + { + OSL_FAIL( + "MigrationEngine_Impl::impl_adjustScriptLibrary_nothrow: unknown script language!" ); + m_rLogger.logRecoverable( MigrationError( + ERR_UNKNOWN_SCRIPT_LANGUAGE, + sScriptLanguage + ) ); + return false; + } + + ::rtl::OUString sLocation = xUri->getParameter( + ::rtl::OUString( RTL_CONSTASCII_USTRINGPARAM( "location" ) ) ); + if ( !sLocation.equalsAsciiL( RTL_CONSTASCII_STRINGPARAM( "document" ) ) ) + { + // only document libraries must be migrated, of course + return false; + } + + ::rtl::OUString sScriptName = xUri->getName(); + sal_Int32 nLibModuleSeparator = sScriptName.indexOf( '.' ); + if ( nLibModuleSeparator < 0 ) + { + OSL_FAIL( + "MigrationEngine_Impl::impl_adjustScriptLibrary_nothrow: invalid/unknown location format!" ); + m_rLogger.logRecoverable( MigrationError( + ERR_UNKNOWN_SCRIPT_NAME_FORMAT, + sScriptName + ) ); + return false; + } + + // replace the library name + ::rtl::OUString sLibrary = sScriptName.copy( 0, nLibModuleSeparator ); + ::rtl::OUString sNewLibName = m_rLogger.getNewLibraryName( + m_nCurrentDocumentID, eScriptType, sLibrary ); + OSL_ENSURE( sLibrary != sNewLibName, + "MigrationEngine_Impl::impl_adjustScriptLibrary_nothrow: a library which has not been migrated?" ); + + ::rtl::OUStringBuffer aNewLocation; + aNewLocation.append( sNewLibName ); + aNewLocation.append( sScriptName.copy( nLibModuleSeparator ) ); + xUri->setName( aNewLocation.makeStringAndClear() ); + + // update the new script URL + _inout_rScriptCode = xUri->getUriReference(); + bSuccess = true; + } + catch( const Exception& ) + { + aException = ::cppu::getCaughtException(); + bSuccess = false; + } + + // log the failure, if any + if ( !bSuccess ) + { + m_rLogger.logRecoverable( MigrationError( + ERR_SCRIPT_TRANSLATION_FAILURE, + _rScriptType, + _inout_rScriptCode, + aException + ) ); + } + + return bSuccess; + } + + //-------------------------------------------------------------------- + bool MigrationEngine_Impl::impl_adjustScriptLibrary_nothrow( ScriptEventDescriptor& _inout_rScriptEvent ) const + { + if ( _inout_rScriptEvent.ScriptType.getLength() && _inout_rScriptEvent.ScriptCode.getLength() ) + return impl_adjustScriptLibrary_nothrow( _inout_rScriptEvent.ScriptType, _inout_rScriptEvent.ScriptCode ); + return false; + } + + //-------------------------------------------------------------------- + bool MigrationEngine_Impl::impl_adjustScriptLibrary_nothrow( Any& _inout_rScriptDescriptor ) const + { + ::comphelper::NamedValueCollection aScriptDesc( _inout_rScriptDescriptor ); + + ::rtl::OUString sScriptType; + ::rtl::OUString sScript; + try + { + OSL_VERIFY( aScriptDesc.get_ensureType( "EventType", sScriptType ) ); + OSL_VERIFY( aScriptDesc.get_ensureType( "Script", sScript ) ); + } + catch( const Exception& ) + { + m_rLogger.logRecoverable( MigrationError( + ERR_INVALID_SCRIPT_DESCRIPTOR_FORMAT, + ::cppu::getCaughtException() + ) ); + } + + if ( sScriptType.getLength() && sScript.getLength() ) + if ( !impl_adjustScriptLibrary_nothrow( sScriptType, sScript ) ) + return false; + + aScriptDesc.put( "Script", sScript ); + _inout_rScriptDescriptor <<= aScriptDesc.getPropertyValues(); + return true; + } + + //-------------------------------------------------------------------- + bool MigrationEngine_Impl::impl_adjustDocumentEvents_nothrow( const SubDocument& _rDocument ) const + { + try + { + Reference< XEventsSupplier > xSuppEvents( _rDocument.xDocument, UNO_QUERY ); + if ( !xSuppEvents.is() ) + // this is allowed. E.g. new-style reports currently do not support this + return true; + + Reference< XNameReplace > xEvents( xSuppEvents->getEvents(), UNO_SET_THROW ); + Sequence< ::rtl::OUString > aEventNames = xEvents->getElementNames(); + + Any aEvent; + for ( const ::rtl::OUString* eventName = aEventNames.getConstArray(); + eventName != aEventNames.getConstArray() + aEventNames.getLength(); + ++eventName + ) + { + aEvent = xEvents->getByName( *eventName ); + if ( !aEvent.hasValue() ) + continue; + + // translate + if ( !impl_adjustScriptLibrary_nothrow( aEvent ) ) + continue; + + // put back + xEvents->replaceByName( *eventName, aEvent ); + } + } + catch( const Exception& ) + { + m_rLogger.logRecoverable( MigrationError( + ERR_ADJUSTING_DOCUMENT_EVENTS_FAILED, + lcl_getSubDocumentDescription( _rDocument ), + ::cppu::getCaughtException() + ) ); + return false; + } + return true; + } + + //-------------------------------------------------------------------- + void MigrationEngine_Impl::impl_adjustDialogElementEvents_throw( const Reference< XInterface >& _rxElement ) const + { + Reference< XScriptEventsSupplier > xEventsSupplier( _rxElement, UNO_QUERY_THROW ); + Reference< XNameReplace > xEvents( xEventsSupplier->getEvents(), UNO_QUERY_THROW ); + Sequence< ::rtl::OUString > aEventNames( xEvents->getElementNames() ); + + const ::rtl::OUString* eventName = aEventNames.getArray(); + const ::rtl::OUString* eventNamesEnd = eventName + aEventNames.getLength(); + + ScriptEventDescriptor aScriptEvent; + for ( ; eventName != eventNamesEnd; ++eventName ) + { + OSL_VERIFY( xEvents->getByName( *eventName ) >>= aScriptEvent ); + + if ( !impl_adjustScriptLibrary_nothrow( aScriptEvent ) ) + continue; + + xEvents->replaceByName( *eventName, makeAny( aScriptEvent ) ); + } + } + + //-------------------------------------------------------------------- + bool MigrationEngine_Impl::impl_adjustDialogEvents_nothrow( Any& _inout_rDialogLibraryElement, + const ::rtl::OUString& _rDocName, const ::rtl::OUString& _rDialogLibName, const ::rtl::OUString& _rDialogName ) const + { + try + { + // load a dialog model from the stream describing it + Reference< XInputStreamProvider > xISP( _inout_rDialogLibraryElement, UNO_QUERY_THROW ); + Reference< XInputStream > xInput( xISP->createInputStream(), UNO_QUERY_THROW ); + + Reference< XNameContainer > xDialogModel( m_aContext.createComponent( "com.sun.star.awt.UnoControlDialogModel" ), UNO_QUERY_THROW ); + ::xmlscript::importDialogModel( xInput, xDialogModel, m_aContext.getUNOContext(), m_xDocumentModel ); + + // adjust the events of the dialog + impl_adjustDialogElementEvents_throw( xDialogModel ); + + // adjust the events of the controls + Sequence< ::rtl::OUString > aControlNames( xDialogModel->getElementNames() ); + const ::rtl::OUString* controlName = aControlNames.getConstArray(); + const ::rtl::OUString* controlNamesEnd = controlName + aControlNames.getLength(); + for ( ; controlName != controlNamesEnd; ++controlName ) + { + impl_adjustDialogElementEvents_throw( Reference< XInterface >( xDialogModel->getByName( *controlName ), UNO_QUERY ) ); + } + + // export dialog model + xISP = ::xmlscript::exportDialogModel( xDialogModel, m_aContext.getUNOContext(), m_xDocumentModel ); + _inout_rDialogLibraryElement <<= xISP; + } + catch( const Exception& ) + { + m_rLogger.logRecoverable( MigrationError( + ERR_ADJUSTING_DIALOG_EVENTS_FAILED, + _rDocName, + _rDialogLibName, + _rDialogName, + ::cppu::getCaughtException() + ) ); + return false; + } + return true; + } + + //-------------------------------------------------------------------- + void MigrationEngine_Impl::impl_adjustFormComponentEvents_throw( const Reference< XIndexAccess >& _rxComponentContainer ) const + { + FormComponentIterator aCompIter( _rxComponentContainer ); + while ( aCompIter.hasMore() ) + { + // 1. adjust the component's scripts of the current component + FormComponentScripts aComponent( aCompIter.next() ); + Sequence< ScriptEventDescriptor > aEvents( aComponent.getEvents() ); + + bool bChangedComponentEvents = false; + for ( ScriptEventDescriptor* scriptEvent = aEvents.getArray(); + scriptEvent != aEvents.getArray() + aEvents.getLength(); + ++scriptEvent + ) + { + if ( !impl_adjustScriptLibrary_nothrow( *scriptEvent ) ) + continue; + + bChangedComponentEvents = true; + } + + if ( bChangedComponentEvents ) + aComponent.setEvents( aEvents ); + + // 2. step down if the component is a container itself + Reference< XIndexAccess > xContainer( aComponent.getComponent(), UNO_QUERY ); + if ( xContainer.is() ) + impl_adjustFormComponentEvents_throw( xContainer ); + } + } + + //-------------------------------------------------------------------- + bool MigrationEngine_Impl::impl_adjustFormComponentEvents_nothrow( const SubDocument& _rDocument ) const + { + try + { + DrawPageIterator aPageIter( _rDocument.xDocument ); + while ( aPageIter.hasMore() ) + { + Reference< XFormsSupplier > xSuppForms( aPageIter.next(), UNO_QUERY_THROW ); + Reference< XIndexAccess > xForms( xSuppForms->getForms(), UNO_QUERY_THROW ); + impl_adjustFormComponentEvents_throw( xForms ); + } + } + catch( const Exception& ) + { + m_rLogger.logRecoverable( MigrationError( + ERR_ADJUSTING_FORMCOMP_EVENTS_FAILED, + lcl_getSubDocumentDescription( _rDocument ), + ::cppu::getCaughtException() + ) ); + return false; + } + return true; + } + + //-------------------------------------------------------------------- + bool MigrationEngine_Impl::impl_unprotectPasswordLibrary_throw( const Reference< XLibraryContainerPassword >& _rxPasswordManager, + const ScriptType _eScriptType, const ::rtl::OUString& _rLibraryName ) const + { + // a human-readable description of the affected library + ::rtl::OUString sLibraryDescription( String( + MacroMigrationResId( STR_LIBRARY_TYPE_AND_NAME ) ) ); + ::comphelper::string::searchAndReplaceAsciiI( sLibraryDescription, "$type$", + getScriptTypeDisplayName( _eScriptType ) ); + ::comphelper::string::searchAndReplaceAsciiI( sLibraryDescription, "$library$", + _rLibraryName ); + + InteractionHandler aHandler( m_aContext, m_xDocumentModel ); + ::rtl::OUString sPassword; + while ( true ) + { + if ( !aHandler.requestDocumentPassword( sLibraryDescription, sPassword ) ) + // aborted by the user + return false; + + bool bSuccessVerification = _rxPasswordManager->verifyLibraryPassword( _rLibraryName, sPassword ); + if ( bSuccessVerification ) + return true; + } + + } + + //==================================================================== + //= MigrationEngine + //==================================================================== + //-------------------------------------------------------------------- + MigrationEngine::MigrationEngine( const ::comphelper::ComponentContext& _rContext, + const Reference< XOfficeDatabaseDocument >& _rxDocument, IMigrationProgress& _rProgress, + MigrationLog& _rLogger ) + :m_pImpl( new MigrationEngine_Impl( _rContext, _rxDocument, _rProgress, _rLogger ) ) + { + } + + //-------------------------------------------------------------------- + MigrationEngine::~MigrationEngine() + { + } + + //-------------------------------------------------------------------- + sal_Int32 MigrationEngine::getFormCount() const + { + return m_pImpl->getFormCount(); + } + + //-------------------------------------------------------------------- + sal_Int32 MigrationEngine::getReportCount() const + { + return m_pImpl->getReportCount(); + } + + //-------------------------------------------------------------------- + bool MigrationEngine::migrateAll() + { + return m_pImpl->migrateAll(); + } + +//........................................................................ +} // namespace dbmm +//........................................................................ + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ |