1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
3 * This file is part of the LibreOffice project.
5 * This Source Code Form is subject to the terms of the Mozilla Public
6 * License, v. 2.0. If a copy of the MPL was not distributed with this
7 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
9 * This file incorporates work covered by the following license notice:
11 * Licensed to the Apache Software Foundation (ASF) under one or more
12 * contributor license agreements. See the NOTICE file distributed
13 * with this work for additional information regarding copyright
14 * ownership. The ASF licenses this file to you under the Apache
15 * License, Version 2.0 (the "License"); you may not use this file
16 * except in compliance with the License. You may obtain a copy of
17 * the License at http://www.apache.org/licenses/LICENSE-2.0 .
20 #include "subcomponentrecovery.hxx"
22 #include <sdbcoretools.hxx>
23 #include "storagexmlstream.hxx"
24 #include "subcomponentloader.hxx"
25 #include "settingsimport.hxx"
27 #include <com/sun/star/embed/ElementModes.hpp>
28 #include <com/sun/star/frame/ModuleManager.hpp>
29 #include <com/sun/star/document/XStorageBasedDocument.hpp>
30 #include <com/sun/star/ucb/XCommandProcessor.hpp>
31 #include <com/sun/star/container/XHierarchicalNameAccess.hpp>
32 #include <com/sun/star/sdb/XFormDocumentsSupplier.hpp>
33 #include <com/sun/star/sdb/XReportDocumentsSupplier.hpp>
34 #include <com/sun/star/xml/sax/XDocumentHandler.hpp>
36 #include <comphelper/namedvaluecollection.hxx>
37 #include <cppuhelper/implbase.hxx>
38 #include <connectivity/dbtools.hxx>
39 #include <comphelper/diagnose_ex.hxx>
40 #include <sal/log.hxx>
41 #include <xmloff/XMLSettingsExportContext.hxx>
42 #include <xmloff/SettingsExportHelper.hxx>
49 using css::uno::Reference
;
50 using css::uno::UNO_QUERY
;
51 using css::uno::UNO_QUERY_THROW
;
52 using css::uno::UNO_SET_THROW
;
53 using css::uno::Exception
;
55 using css::uno::Sequence
;
56 using css::uno::XComponentContext
;
57 using css::embed::XStorage
;
58 using css::sdb::application::XDatabaseDocumentUI
;
59 using css::beans::Pair
;
60 using css::frame::ModuleManager
;
61 using css::frame::XModuleManager2
;
62 using css::lang::XComponent
;
63 using css::frame::XModel
;
64 using css::frame::XController
;
65 using css::beans::XPropertySet
;
66 using css::beans::PropertyValue
;
67 using css::document::XStorageBasedDocument
;
68 using css::ucb::XCommandProcessor
;
69 using css::container::XHierarchicalNameAccess
;
70 using css::sdb::XFormDocumentsSupplier
;
71 using css::sdb::XReportDocumentsSupplier
;
72 using css::xml::sax::XLocator
;
73 using css::xml::sax::XDocumentHandler
;
74 using css::xml::sax::XAttributeList
;
76 namespace ElementModes
= css::embed::ElementModes
;
77 namespace DatabaseObject
= css::sdb::application::DatabaseObject
;
82 OUString
lcl_getComponentStorageBaseName( const SubComponentType i_eType
)
89 return u
"report"_ustr
;
98 OSL_FAIL( "lcl_getComponentStorageBaseName: unimplemented case!" );
102 SubComponentType
lcl_databaseObjectToSubComponentType( const sal_Int32 i_nObjectType
)
104 switch ( i_nObjectType
)
106 case DatabaseObject::TABLE
: return TABLE
;
107 case DatabaseObject::QUERY
: return QUERY
;
108 case DatabaseObject::FORM
: return FORM
;
109 case DatabaseObject::REPORT
:return REPORT
;
116 bool lcl_determineReadOnly( const Reference
< XComponent
>& i_rComponent
)
118 Reference
< XModel
> xDocument( i_rComponent
, UNO_QUERY
);
119 if ( !xDocument
.is() )
121 Reference
< XController
> xController( i_rComponent
, UNO_QUERY_THROW
);
122 xDocument
= xController
->getModel();
125 if ( !xDocument
.is() )
128 ::comphelper::NamedValueCollection
aDocArgs( xDocument
->getArgs() );
129 return aDocArgs
.getOrDefault( u
"ReadOnly"_ustr
, false );
132 Reference
< XCommandProcessor
> lcl_getSubComponentDef_nothrow( const Reference
< XDatabaseDocumentUI
>& i_rAppUI
,
133 const SubComponentType i_eType
, const OUString
& i_rName
)
135 Reference
< XController
> xController( i_rAppUI
, UNO_QUERY_THROW
);
136 ENSURE_OR_RETURN( ( i_eType
== FORM
) || ( i_eType
== REPORT
), "lcl_getSubComponentDef_nothrow: illegal controller", nullptr );
138 Reference
< XCommandProcessor
> xCommandProcessor
;
141 Reference
< XHierarchicalNameAccess
> xDefinitionContainer
;
142 if ( i_eType
== FORM
)
144 Reference
< XFormDocumentsSupplier
> xSuppForms( xController
->getModel(), UNO_QUERY_THROW
);
145 xDefinitionContainer
.set( xSuppForms
->getFormDocuments(), UNO_QUERY_THROW
);
149 Reference
< XReportDocumentsSupplier
> xSuppReports( xController
->getModel(), UNO_QUERY_THROW
);
150 xDefinitionContainer
.set( xSuppReports
->getReportDocuments(), UNO_QUERY_THROW
);
152 xCommandProcessor
.set( xDefinitionContainer
->getByHierarchicalName( i_rName
), UNO_QUERY_THROW
);
154 catch( const Exception
& )
156 DBG_UNHANDLED_EXCEPTION("dbaccess");
158 return xCommandProcessor
;
161 constexpr OUString sSettingsStreamName
= u
"settings.xml"_ustr
;
162 constexpr OUString sCurrentQueryDesignName
= u
"ooo:current-query-design"_ustr
;
167 // SettingsExportContext
168 class SettingsExportContext
: public ::xmloff::XMLSettingsExportContext
171 SettingsExportContext( const Reference
<XComponentContext
>& i_rContext
, StorageXMLOutputStream
& i_rDelegator
)
172 :m_rContext( i_rContext
)
173 ,m_rDelegator( i_rDelegator
)
174 ,m_aNamespace( ::xmloff::token::GetXMLToken( ::xmloff::token::XML_NP_CONFIG
) )
178 virtual ~SettingsExportContext()
183 virtual void AddAttribute( enum ::xmloff::token::XMLTokenEnum i_eName
, const OUString
& i_rValue
) override
;
184 virtual void AddAttribute( enum ::xmloff::token::XMLTokenEnum i_eName
, enum ::xmloff::token::XMLTokenEnum i_eValue
) override
;
185 virtual void StartElement( enum ::xmloff::token::XMLTokenEnum i_eName
) override
;
186 virtual void EndElement ( const bool i_bIgnoreWhitespace
) override
;
187 virtual void Characters( const OUString
& i_rCharacters
) override
;
189 virtual css::uno::Reference
< css::uno::XComponentContext
>
190 GetComponentContext() const override
;
193 OUString
impl_prefix( const ::xmloff::token::XMLTokenEnum i_eToken
)
195 return m_aNamespace
+ ":" + ::xmloff::token::GetXMLToken( i_eToken
);
199 const Reference
<XComponentContext
>& m_rContext
;
200 StorageXMLOutputStream
& m_rDelegator
;
201 const OUString m_aNamespace
;
206 void SettingsExportContext::AddAttribute( enum ::xmloff::token::XMLTokenEnum i_eName
, const OUString
& i_rValue
)
208 m_rDelegator
.addAttribute( impl_prefix( i_eName
), i_rValue
);
211 void SettingsExportContext::AddAttribute( enum ::xmloff::token::XMLTokenEnum i_eName
, enum ::xmloff::token::XMLTokenEnum i_eValue
)
213 m_rDelegator
.addAttribute( impl_prefix( i_eName
), ::xmloff::token::GetXMLToken( i_eValue
) );
216 void SettingsExportContext::StartElement( enum ::xmloff::token::XMLTokenEnum i_eName
)
218 m_rDelegator
.ignorableWhitespace( u
" "_ustr
);
220 m_rDelegator
.startElement( impl_prefix( i_eName
) );
223 void SettingsExportContext::EndElement( const bool i_bIgnoreWhitespace
)
225 if ( i_bIgnoreWhitespace
)
226 m_rDelegator
.ignorableWhitespace( u
" "_ustr
);
227 m_rDelegator
.endElement();
230 void SettingsExportContext::Characters( const OUString
& i_rCharacters
)
232 m_rDelegator
.characters( i_rCharacters
);
235 Reference
< css::uno::XComponentContext
> SettingsExportContext::GetComponentContext() const
240 // SettingsDocumentHandler
241 typedef ::cppu::WeakImplHelper
< XDocumentHandler
242 > SettingsDocumentHandler_Base
;
246 class SettingsDocumentHandler
: public SettingsDocumentHandler_Base
249 SettingsDocumentHandler()
254 virtual ~SettingsDocumentHandler() override
260 virtual void SAL_CALL
startDocument( ) override
;
261 virtual void SAL_CALL
endDocument( ) override
;
262 virtual void SAL_CALL
startElement( const OUString
& aName
, const Reference
< XAttributeList
>& xAttribs
) override
;
263 virtual void SAL_CALL
endElement( const OUString
& aName
) override
;
264 virtual void SAL_CALL
characters( const OUString
& aChars
) override
;
265 virtual void SAL_CALL
ignorableWhitespace( const OUString
& aWhitespaces
) override
;
266 virtual void SAL_CALL
processingInstruction( const OUString
& aTarget
, const OUString
& aData
) override
;
267 virtual void SAL_CALL
setDocumentLocator( const Reference
< XLocator
>& xLocator
) override
;
269 const ::comphelper::NamedValueCollection
& getSettings() const { return m_aSettings
; }
272 std::stack
< ::rtl::Reference
< SettingsImport
> > m_aStates
;
273 ::comphelper::NamedValueCollection m_aSettings
;
278 void SAL_CALL
SettingsDocumentHandler::startDocument( )
282 void SAL_CALL
SettingsDocumentHandler::endDocument( )
286 void SAL_CALL
SettingsDocumentHandler::startElement( const OUString
& i_Name
, const Reference
< XAttributeList
>& i_Attribs
)
288 ::rtl::Reference
< SettingsImport
> pNewState
;
290 if ( m_aStates
.empty() )
292 if ( i_Name
== "office:settings" )
294 pNewState
= new OfficeSettingsImport( m_aSettings
);
298 OSL_FAIL( "SettingsDocumentHandler::startElement: invalid settings file!" );
299 // Yes, that's not correct. Somebody could, in theory, give us a document which starts with "foo:settings",
300 // where "foo" is mapped to the proper namespace URL.
301 // However, there's no need to bother with this. The "recovery" sub storage we're recovering from is
302 // not part of ODF, so we can impose any format restrictions on it ...
307 ::rtl::Reference
< SettingsImport
> pCurrentState( m_aStates
.top() );
308 pNewState
= pCurrentState
->nextState( i_Name
);
311 ENSURE_OR_THROW( pNewState
.is(), "no new state - aborting import" );
312 pNewState
->startElement( i_Attribs
);
314 m_aStates
.push( pNewState
);
317 void SAL_CALL
SettingsDocumentHandler::endElement( const OUString
& )
319 ENSURE_OR_THROW( !m_aStates
.empty(), "no active element" );
321 ::rtl::Reference
< SettingsImport
> pCurrentState( m_aStates
.top() );
322 pCurrentState
->endElement();
326 void SAL_CALL
SettingsDocumentHandler::characters( const OUString
& i_Chars
)
328 ENSURE_OR_THROW( !m_aStates
.empty(), "no active element" );
330 ::rtl::Reference
< SettingsImport
> pCurrentState( m_aStates
.top() );
331 pCurrentState
->characters( i_Chars
);
334 void SAL_CALL
SettingsDocumentHandler::ignorableWhitespace( const OUString
& )
336 // ignore them - that's why they're called "ignorable"
339 void SAL_CALL
SettingsDocumentHandler::processingInstruction( const OUString
&, const OUString
& )
341 OSL_FAIL( "SettingsDocumentHandler::processingInstruction: unexpected ..." );
344 void SAL_CALL
SettingsDocumentHandler::setDocumentLocator( const Reference
< XLocator
>& ) {}
346 // SubComponentRecovery
347 OUString
SubComponentRecovery::getComponentsStorageName( const SubComponentType i_eType
)
352 return u
"forms"_ustr
;
354 return u
"reports"_ustr
;
356 return u
"tables"_ustr
;
358 return u
"queries"_ustr
;
359 case RELATION_DESIGN
:
360 return u
"relations"_ustr
;
365 OSL_FAIL( "SubComponentRecovery::getComponentsStorageName: unimplemented case!" );
369 void SubComponentRecovery::saveToRecoveryStorage( const Reference
< XStorage
>& i_rRecoveryStorage
,
370 MapCompTypeToCompDescs
& io_mapCompDescs
)
372 if ( m_eType
== UNKNOWN
)
373 // quite fatal, but has already been reported (as assertion) before
376 // open the sub storage for the given kind of components
377 const OUString
aStorageName( getComponentsStorageName( m_eType
) );
378 const Reference
< XStorage
> xComponentsStorage( i_rRecoveryStorage
->openStorageElement(
379 aStorageName
, ElementModes::READWRITE
), UNO_SET_THROW
);
381 // find a free sub storage name, and create Yet Another Sub Storage
382 const OUString
aBaseName( lcl_getComponentStorageBaseName( m_eType
) );
383 const OUString sStorName
= ::dbtools::createUniqueName( xComponentsStorage
, aBaseName
);
384 const Reference
< XStorage
> xObjectStor( xComponentsStorage
->openStorageElement(
385 sStorName
, ElementModes::READWRITE
), UNO_SET_THROW
);
391 impl_saveSubDocument_throw( xObjectStor
);
395 impl_saveQueryDesign_throw( xObjectStor
);
400 OSL_FAIL( "SubComponentRecoverys::saveToRecoveryStorage: unimplemented case!" );
404 // commit the storage(s)
405 tools::stor::commitStorageIfWriteable( xObjectStor
);
406 tools::stor::commitStorageIfWriteable( xComponentsStorage
);
408 // remember the relationship from the component name to the storage name
409 MapStringToCompDesc
& rMapCompDescs
= io_mapCompDescs
[ m_eType
];
410 OSL_ENSURE( rMapCompDescs
.find( sStorName
) == rMapCompDescs
.end(),
411 "SubComponentRecoverys::saveToRecoveryStorage: object name already used!" );
412 rMapCompDescs
[ sStorName
] = m_aCompDesc
;
415 void SubComponentRecovery::impl_identifyComponent_throw()
417 // ask the controller
418 Pair
< sal_Int32
, OUString
> aComponentIdentity
= m_xDocumentUI
->identifySubComponent( m_xComponent
);
419 m_eType
= lcl_databaseObjectToSubComponentType( aComponentIdentity
.First
);
420 m_aCompDesc
.sName
= aComponentIdentity
.Second
;
422 // what the controller didn't give us is the information whether this is in edit mode or not ...
423 Reference
< XModuleManager2
> xModuleManager( ModuleManager::create(m_rContext
) );
424 const OUString sModuleIdentifier
= xModuleManager
->identify( m_xComponent
);
429 m_aCompDesc
.bForEditing
= sModuleIdentifier
== "com.sun.star.sdb.TableDesign";
433 m_aCompDesc
.bForEditing
= sModuleIdentifier
== "com.sun.star.sdb.QueryDesign";
437 if ( sModuleIdentifier
== "com.sun.star.report.ReportDefinition" )
439 // it's an SRB report designer
440 m_aCompDesc
.bForEditing
= true;
446 m_aCompDesc
.bForEditing
= !lcl_determineReadOnly( m_xComponent
);
450 if ( sModuleIdentifier
== "com.sun.star.sdb.RelationDesign" )
452 m_eType
= RELATION_DESIGN
;
453 m_aCompDesc
.bForEditing
= true;
457 OSL_FAIL( "SubComponentRecovery::impl_identifyComponent_throw: couldn't classify the given sub component!" );
462 SAL_WARN_IF( m_eType
== UNKNOWN
, "dbaccess",
463 "SubComponentRecovery::impl_identifyComponent_throw: couldn't classify the component!" );
466 void SubComponentRecovery::impl_saveQueryDesign_throw( const Reference
< XStorage
>& i_rObjectStorage
)
468 ENSURE_OR_THROW( m_eType
== QUERY
, "illegal sub component type" );
469 ENSURE_OR_THROW( i_rObjectStorage
.is(), "illegal storage" );
471 // retrieve the current query design (which might differ from what we can retrieve as ActiveCommand property, since
472 // the latter is updated only upon successful save of the design)
473 Reference
< XPropertySet
> xDesignerProps( m_xComponent
, UNO_QUERY_THROW
);
474 Sequence
< PropertyValue
> aCurrentQueryDesign
;
475 OSL_VERIFY( xDesignerProps
->getPropertyValue( u
"CurrentQueryDesign"_ustr
) >>= aCurrentQueryDesign
);
477 // write the query design
478 StorageXMLOutputStream
aDesignOutput( m_rContext
, i_rObjectStorage
, sSettingsStreamName
);
479 SettingsExportContext
aSettingsExportContext( m_rContext
, aDesignOutput
);
481 static constexpr OUString
sWhitespace( u
" "_ustr
);
483 aDesignOutput
.startElement( u
"office:settings"_ustr
);
484 aDesignOutput
.ignorableWhitespace( sWhitespace
);
486 XMLSettingsExportHelper
aSettingsExporter( aSettingsExportContext
);
487 aSettingsExporter
.exportAllSettings( aCurrentQueryDesign
, sCurrentQueryDesignName
);
489 aDesignOutput
.ignorableWhitespace( sWhitespace
);
490 aDesignOutput
.endElement();
491 aDesignOutput
.close();
494 void SubComponentRecovery::impl_saveSubDocument_throw( const Reference
< XStorage
>& i_rObjectStorage
)
496 ENSURE_OR_THROW( ( m_eType
== FORM
) || ( m_eType
== REPORT
), "illegal sub component type" );
497 ENSURE_OR_THROW( i_rObjectStorage
.is(), "illegal storage" );
499 // store the document into the storage
500 Reference
< XStorageBasedDocument
> xStorageDocument( m_xComponent
, UNO_QUERY_THROW
);
501 xStorageDocument
->storeToStorage( i_rObjectStorage
, Sequence
< PropertyValue
>() );
504 Reference
< XComponent
> SubComponentRecovery::impl_recoverSubDocument_throw( const Reference
< XStorage
>& i_rRecoveryStorage
,
505 const OUString
& i_rComponentName
, const bool i_bForEditing
)
507 Reference
< XComponent
> xSubComponent
;
508 Reference
< XCommandProcessor
> xDocDefinition
;
510 ::comphelper::NamedValueCollection aLoadArgs
;
511 aLoadArgs
.put( u
"RecoveryStorage"_ustr
, i_rRecoveryStorage
);
513 // load/create the sub component hidden. We'll show it when the main app window is shown.
514 aLoadArgs
.put( u
"Hidden"_ustr
, true );
516 if ( !i_rComponentName
.isEmpty() )
518 xDocDefinition
= lcl_getSubComponentDef_nothrow( m_xDocumentUI
, m_eType
, i_rComponentName
);
519 xSubComponent
.set( m_xDocumentUI
->loadComponentWithArguments(
523 aLoadArgs
.getPropertyValues()
530 Reference
< XComponent
> xDocDefComponent
;
531 xSubComponent
.set( m_xDocumentUI
->createComponentWithArguments(
533 aLoadArgs
.getPropertyValues(),
539 xDocDefinition
.set( xDocDefComponent
, UNO_QUERY
);
540 OSL_ENSURE( xDocDefinition
.is(), "DatabaseDocumentRecovery::recoverSubDocuments: loaded a form/report, but don't have a document definition?!" );
543 if ( xDocDefinition
.is() )
545 Reference
< XController
> xController( m_xDocumentUI
, UNO_QUERY_THROW
);
546 rtl::Reference( new SubComponentLoader( xController
, xDocDefinition
) );
549 return xSubComponent
;
552 Reference
< XComponent
> SubComponentRecovery::impl_recoverQueryDesign_throw( const Reference
< XStorage
>& i_rRecoveryStorage
,
553 const OUString
& i_rComponentName
, const bool i_bForEditing
)
555 Reference
< XComponent
> xSubComponent
;
557 // first read the settings query design settings from the storage
558 StorageXMLInputStream
aDesignInput( m_rContext
, i_rRecoveryStorage
, sSettingsStreamName
);
560 ::rtl::Reference
< SettingsDocumentHandler
> pDocHandler( new SettingsDocumentHandler
);
561 aDesignInput
.import( pDocHandler
);
563 const ::comphelper::NamedValueCollection
& rSettings( pDocHandler
->getSettings() );
564 const Any
& aCurrentQueryDesign
= rSettings
.get( sCurrentQueryDesignName
);
565 #if OSL_DEBUG_LEVEL > 0
566 Sequence
< PropertyValue
> aQueryDesignLayout
;
567 OSL_VERIFY( aCurrentQueryDesign
>>= aQueryDesignLayout
);
570 // then load the query designer
571 ::comphelper::NamedValueCollection aLoadArgs
;
572 aLoadArgs
.put( u
"CurrentQueryDesign"_ustr
, aCurrentQueryDesign
);
573 aLoadArgs
.put( u
"Hidden"_ustr
, true );
575 if ( !i_rComponentName
.isEmpty() )
577 xSubComponent
.set( m_xDocumentUI
->loadComponentWithArguments(
581 aLoadArgs
.getPropertyValues()
588 Reference
< XComponent
> xDummy
;
589 xSubComponent
.set( m_xDocumentUI
->createComponentWithArguments(
591 aLoadArgs
.getPropertyValues(),
598 Reference
< XController
> xController( m_xDocumentUI
, UNO_QUERY_THROW
);
599 rtl::Reference( new SubComponentLoader( xController
, xSubComponent
) );
601 return xSubComponent
;
604 Reference
< XComponent
> SubComponentRecovery::recoverFromStorage( const Reference
< XStorage
>& i_rRecoveryStorage
,
605 const OUString
& i_rComponentName
, const bool i_bForEditing
)
607 Reference
< XComponent
> xSubComponent
;
612 xSubComponent
= impl_recoverSubDocument_throw( i_rRecoveryStorage
, i_rComponentName
, i_bForEditing
);
615 xSubComponent
= impl_recoverQueryDesign_throw( i_rRecoveryStorage
, i_rComponentName
, i_bForEditing
);
618 OSL_FAIL( "SubComponentRecovery::recoverFromStorage: unimplemented case!" );
621 return xSubComponent
;
624 } // namespace dbaccess
626 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */