1 /************************************************************** 2 * 3 * Licensed to the Apache Software Foundation (ASF) under one 4 * or more contributor license agreements. See the NOTICE file 5 * distributed with this work for additional information 6 * regarding copyright ownership. The ASF licenses this file 7 * to you under the Apache License, Version 2.0 (the 8 * "License"); you may not use this file except in compliance 9 * with the License. You may obtain a copy of the License at 10 * 11 * http://www.apache.org/licenses/LICENSE-2.0 12 * 13 * Unless required by applicable law or agreed to in writing, 14 * software distributed under the License is distributed on an 15 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 16 * KIND, either express or implied. See the License for the 17 * specific language governing permissions and limitations 18 * under the License. 19 * 20 *************************************************************/ 21 22 23 24 #include "precompiled_dbaccess.hxx" 25 26 #include "recovery/dbdocrecovery.hxx" 27 #include "sdbcoretools.hxx" 28 #include "storagetextstream.hxx" 29 #include "subcomponentrecovery.hxx" 30 #include "subcomponents.hxx" 31 #include "dbastrings.hrc" 32 33 /** === begin UNO includes === **/ 34 #include <com/sun/star/sdb/application/XDatabaseDocumentUI.hpp> 35 #include <com/sun/star/embed/ElementModes.hpp> 36 #include <com/sun/star/document/XStorageBasedDocument.hpp> 37 #include <com/sun/star/io/XTextOutputStream.hpp> 38 #include <com/sun/star/io/XTextInputStream.hpp> 39 #include <com/sun/star/io/XActiveDataSource.hpp> 40 #include <com/sun/star/io/XActiveDataSink.hpp> 41 #include <com/sun/star/util/XModifiable.hpp> 42 #include <com/sun/star/beans/XPropertySet.hpp> 43 /** === end UNO includes === **/ 44 45 #include <comphelper/componentcontext.hxx> 46 #include <comphelper/namedvaluecollection.hxx> 47 #include <rtl/ustrbuf.hxx> 48 #include <tools/diagnose_ex.h> 49 50 #include <algorithm> 51 52 //........................................................................ 53 namespace dbaccess 54 { 55 //........................................................................ 56 57 /** === begin UNO using === **/ 58 using ::com::sun::star::uno::Reference; 59 using ::com::sun::star::uno::XInterface; 60 using ::com::sun::star::uno::UNO_QUERY; 61 using ::com::sun::star::uno::UNO_QUERY_THROW; 62 using ::com::sun::star::uno::UNO_SET_THROW; 63 using ::com::sun::star::uno::Exception; 64 using ::com::sun::star::uno::RuntimeException; 65 using ::com::sun::star::uno::Any; 66 using ::com::sun::star::uno::makeAny; 67 using ::com::sun::star::uno::Sequence; 68 using ::com::sun::star::uno::Type; 69 using ::com::sun::star::embed::XStorage; 70 using ::com::sun::star::frame::XController; 71 using ::com::sun::star::sdb::application::XDatabaseDocumentUI; 72 using ::com::sun::star::lang::XComponent; 73 using ::com::sun::star::document::XStorageBasedDocument; 74 using ::com::sun::star::beans::PropertyValue; 75 using ::com::sun::star::io::XStream; 76 using ::com::sun::star::io::XTextOutputStream; 77 using ::com::sun::star::io::XActiveDataSource; 78 using ::com::sun::star::io::XTextInputStream; 79 using ::com::sun::star::io::XActiveDataSink; 80 using ::com::sun::star::frame::XModel; 81 using ::com::sun::star::util::XModifiable; 82 using ::com::sun::star::beans::XPropertySet; 83 using ::com::sun::star::lang::XMultiServiceFactory; 84 /** === end UNO using === **/ 85 86 namespace ElementModes = ::com::sun::star::embed::ElementModes; 87 88 //==================================================================== 89 //= helpers 90 //==================================================================== 91 namespace 92 { 93 // ......................................................................... lcl_getPersistentRepresentation(const MapStringToCompDesc::value_type & i_rComponentDesc,::rtl::OUStringBuffer & o_rBuffer)94 static void lcl_getPersistentRepresentation( const MapStringToCompDesc::value_type& i_rComponentDesc, ::rtl::OUStringBuffer& o_rBuffer ) 95 { 96 o_rBuffer.append( i_rComponentDesc.first ); 97 o_rBuffer.append( sal_Unicode( '=' ) ); 98 o_rBuffer.append( i_rComponentDesc.second.sName ); 99 o_rBuffer.append( sal_Unicode( ',' ) ); 100 o_rBuffer.append( sal_Unicode( i_rComponentDesc.second.bForEditing ? '1' : '0' ) ); 101 } 102 103 // ......................................................................... lcl_extractCompDesc(const::rtl::OUString & i_rIniLine,::rtl::OUString & o_rStorName,SubComponentDescriptor & o_rCompDesc)104 static bool lcl_extractCompDesc( const ::rtl::OUString& i_rIniLine, ::rtl::OUString& o_rStorName, SubComponentDescriptor& o_rCompDesc ) 105 { 106 const sal_Int32 nEqualSignPos = i_rIniLine.indexOf( sal_Unicode( '=' ) ); 107 if ( nEqualSignPos < 1 ) 108 { 109 OSL_ENSURE( false, "lcl_extractCompDesc: invalid map file entry - unexpected pos of '='" ); 110 return false; 111 } 112 o_rStorName = i_rIniLine.copy( 0, nEqualSignPos ); 113 114 const sal_Int32 nCommaPos = i_rIniLine.lastIndexOf( sal_Unicode( ',' ) ); 115 if ( nCommaPos != i_rIniLine.getLength() - 2 ) 116 { 117 OSL_ENSURE( false, "lcl_extractCompDesc: invalid map file entry - unexpected pos of ','" ); 118 return false; 119 } 120 o_rCompDesc.sName = i_rIniLine.copy( nEqualSignPos + 1, nCommaPos - nEqualSignPos - 1 ); 121 o_rCompDesc.bForEditing = ( i_rIniLine.getStr()[ nCommaPos + 1 ] == '1' ); 122 return true; 123 } 124 125 // ......................................................................... lcl_getRecoveryDataSubStorageName()126 static const ::rtl::OUString& lcl_getRecoveryDataSubStorageName() 127 { 128 static const ::rtl::OUString s_sRecDataStorName( RTL_CONSTASCII_USTRINGPARAM( "recovery" ) ); 129 return s_sRecDataStorName; 130 } 131 // ......................................................................... lcl_getObjectMapStreamName()132 static const ::rtl::OUString& lcl_getObjectMapStreamName() 133 { 134 static const ::rtl::OUString s_sObjectMapStreamName( RTL_CONSTASCII_USTRINGPARAM( "storage-component-map.ini" ) ); 135 return s_sObjectMapStreamName; 136 } 137 138 // ......................................................................... lcl_getMapStreamEncodingName()139 static const ::rtl::OUString& lcl_getMapStreamEncodingName() 140 { 141 static const ::rtl::OUString s_sMapStreamEncodingName( RTL_CONSTASCII_USTRINGPARAM( "UTF-8" ) ); 142 return s_sMapStreamEncodingName; 143 } 144 145 // ......................................................................... lcl_writeObjectMap_throw(const::comphelper::ComponentContext & i_rContext,const Reference<XStorage> & i_rStorage,const MapStringToCompDesc & i_mapStorageToCompDesc)146 static void lcl_writeObjectMap_throw( const ::comphelper::ComponentContext& i_rContext, const Reference< XStorage >& i_rStorage, 147 const MapStringToCompDesc& i_mapStorageToCompDesc ) 148 { 149 if ( i_mapStorageToCompDesc.empty() ) 150 // nothing to do 151 return; 152 153 StorageTextOutputStream aTextOutput( i_rContext, i_rStorage, lcl_getObjectMapStreamName() ); 154 155 aTextOutput.writeLine( ::rtl::OUString( RTL_CONSTASCII_USTRINGPARAM( "[storages]" ) ) ); 156 157 for ( MapStringToCompDesc::const_iterator stor = i_mapStorageToCompDesc.begin(); 158 stor != i_mapStorageToCompDesc.end(); 159 ++stor 160 ) 161 { 162 ::rtl::OUStringBuffer aLine; 163 lcl_getPersistentRepresentation( *stor, aLine ); 164 165 aTextOutput.writeLine( aLine.makeStringAndClear() ); 166 } 167 168 aTextOutput.writeLine(); 169 } 170 171 // ......................................................................... lcl_isSectionStart(const::rtl::OUString & i_rIniLine,::rtl::OUString & o_rSectionName)172 static bool lcl_isSectionStart( const ::rtl::OUString& i_rIniLine, ::rtl::OUString& o_rSectionName ) 173 { 174 const sal_Int32 nLen = i_rIniLine.getLength(); 175 if ( ( nLen > 0 ) && ( i_rIniLine.getStr()[0] == '[' ) && ( i_rIniLine.getStr()[ nLen - 1 ] == ']' ) ) 176 { 177 o_rSectionName = i_rIniLine.copy( 1, nLen -2 ); 178 return true; 179 } 180 return false; 181 } 182 183 // ......................................................................... lcl_stripTrailingLineFeed(::rtl::OUString & io_rLine)184 static void lcl_stripTrailingLineFeed( ::rtl::OUString& io_rLine ) 185 { 186 const sal_Int32 nLen = io_rLine.getLength(); 187 if ( ( nLen > 0 ) && ( io_rLine.getStr()[ nLen - 1 ] == '\n' ) ) 188 io_rLine = io_rLine.copy( 0, nLen - 1 ); 189 } 190 191 // ......................................................................... lcl_readObjectMap_throw(const::comphelper::ComponentContext & i_rContext,const Reference<XStorage> & i_rStorage,MapStringToCompDesc & o_mapStorageToObjectName)192 static void lcl_readObjectMap_throw( const ::comphelper::ComponentContext& i_rContext, const Reference< XStorage >& i_rStorage, 193 MapStringToCompDesc& o_mapStorageToObjectName ) 194 { 195 ENSURE_OR_THROW( i_rStorage.is(), "invalid storage" ); 196 if ( !i_rStorage->hasByName( lcl_getObjectMapStreamName() ) ) 197 { // nothing to do, though suspicious 198 OSL_ENSURE( false, "lcl_readObjectMap_throw: if there's no map file, then there's expected to be no storage, too!" ); 199 return; 200 } 201 202 Reference< XStream > xIniStream( i_rStorage->openStreamElement( 203 lcl_getObjectMapStreamName(), ElementModes::READ ), UNO_SET_THROW ); 204 205 Reference< XTextInputStream > xTextInput( i_rContext.createComponent( "com.sun.star.io.TextInputStream" ), UNO_QUERY_THROW ); 206 xTextInput->setEncoding( lcl_getMapStreamEncodingName() ); 207 208 Reference< XActiveDataSink > xDataSink( xTextInput, UNO_QUERY_THROW ); 209 xDataSink->setInputStream( xIniStream->getInputStream() ); 210 211 ::rtl::OUString sCurrentSection; 212 bool bCurrentSectionIsKnownToBeUnsupported = true; 213 while ( !xTextInput->isEOF() ) 214 { 215 ::rtl::OUString sLine = xTextInput->readLine(); 216 lcl_stripTrailingLineFeed( sLine ); 217 218 if ( sLine.getLength() == 0 ) 219 continue; 220 221 if ( lcl_isSectionStart( sLine, sCurrentSection ) ) 222 { 223 bCurrentSectionIsKnownToBeUnsupported = false; 224 continue; 225 } 226 227 if ( bCurrentSectionIsKnownToBeUnsupported ) 228 continue; 229 230 // the only section we support so far is "storages" 231 if ( !sCurrentSection.equalsAscii( "storages" ) ) 232 { 233 bCurrentSectionIsKnownToBeUnsupported = true; 234 continue; 235 } 236 237 ::rtl::OUString sStorageName; 238 SubComponentDescriptor aCompDesc; 239 if ( !lcl_extractCompDesc( sLine, sStorageName, aCompDesc ) ) 240 continue; 241 o_mapStorageToObjectName[ sStorageName ] = aCompDesc; 242 } 243 } 244 245 // ......................................................................... lcl_markModified(const Reference<XComponent> & i_rSubComponent)246 static void lcl_markModified( const Reference< XComponent >& i_rSubComponent ) 247 { 248 const Reference< XModifiable > xModify( i_rSubComponent, UNO_QUERY ); 249 if ( !xModify.is() ) 250 { 251 OSL_ENSURE( false, "lcl_markModified: unhandled case!" ); 252 return; 253 } 254 255 xModify->setModified( sal_True ); 256 } 257 } 258 259 //==================================================================== 260 //= DatabaseDocumentRecovery_Data 261 //==================================================================== 262 struct DBACCESS_DLLPRIVATE DatabaseDocumentRecovery_Data 263 { 264 const ::comphelper::ComponentContext aContext; 265 DatabaseDocumentRecovery_Datadbaccess::DatabaseDocumentRecovery_Data266 DatabaseDocumentRecovery_Data( const ::comphelper::ComponentContext& i_rContext ) 267 :aContext( i_rContext ) 268 { 269 } 270 }; 271 272 //==================================================================== 273 //= DatabaseDocumentRecovery 274 //==================================================================== 275 //-------------------------------------------------------------------- DatabaseDocumentRecovery(const::comphelper::ComponentContext & i_rContext)276 DatabaseDocumentRecovery::DatabaseDocumentRecovery( const ::comphelper::ComponentContext& i_rContext ) 277 :m_pData( new DatabaseDocumentRecovery_Data( i_rContext ) ) 278 { 279 } 280 281 //-------------------------------------------------------------------- ~DatabaseDocumentRecovery()282 DatabaseDocumentRecovery::~DatabaseDocumentRecovery() 283 { 284 } 285 286 //-------------------------------------------------------------------- saveModifiedSubComponents(const Reference<XStorage> & i_rTargetStorage,const::std::vector<Reference<XController>> & i_rControllers)287 void DatabaseDocumentRecovery::saveModifiedSubComponents( const Reference< XStorage >& i_rTargetStorage, 288 const ::std::vector< Reference< XController > >& i_rControllers ) 289 { 290 ENSURE_OR_THROW( i_rTargetStorage.is(), "invalid document storage" ); 291 292 // create a sub storage for recovery data 293 if ( i_rTargetStorage->hasByName( lcl_getRecoveryDataSubStorageName() ) ) 294 i_rTargetStorage->removeElement( lcl_getRecoveryDataSubStorageName() ); 295 Reference< XStorage > xRecoveryStorage = i_rTargetStorage->openStorageElement( lcl_getRecoveryDataSubStorageName(), ElementModes::READWRITE ); 296 297 // store recovery data for open sub components of the given controller(s) 298 if ( !i_rControllers.empty() ) 299 { 300 ENSURE_OR_THROW( i_rControllers.size() == 1, "can't handle more than one controller" ); 301 // At the moment, there can be only one view to a database document. If we ever allow for more than this, 302 // then we need a concept for sub documents opened from different controllers (i.e. two document views, 303 // and the user opens the very same form in both views). And depending on this, we need a concept for 304 // how those are saved to the recovery file. 305 306 MapCompTypeToCompDescs aMapCompDescs; 307 308 for ( ::std::vector< Reference< XController > >::const_iterator ctrl = i_rControllers.begin(); 309 ctrl != i_rControllers.end(); 310 ++ctrl 311 ) 312 { 313 Reference< XDatabaseDocumentUI > xDatabaseUI( *ctrl, UNO_QUERY_THROW ); 314 Sequence< Reference< XComponent > > aComponents( xDatabaseUI->getSubComponents() ); 315 316 const Reference< XComponent >* component = aComponents.getConstArray(); 317 const Reference< XComponent >* componentEnd = aComponents.getConstArray() + aComponents.getLength(); 318 for ( ; component != componentEnd; ++component ) 319 { 320 SubComponentRecovery aComponentRecovery( m_pData->aContext, xDatabaseUI, *component ); 321 aComponentRecovery.saveToRecoveryStorage( xRecoveryStorage, aMapCompDescs ); 322 } 323 } 324 325 for ( MapCompTypeToCompDescs::const_iterator map = aMapCompDescs.begin(); 326 map != aMapCompDescs.end(); 327 ++map 328 ) 329 { 330 Reference< XStorage > xComponentsStor( xRecoveryStorage->openStorageElement( 331 SubComponentRecovery::getComponentsStorageName( map->first ), ElementModes::WRITE | ElementModes::NOCREATE ) ); 332 lcl_writeObjectMap_throw( m_pData->aContext, xComponentsStor, map->second ); 333 tools::stor::commitStorageIfWriteable( xComponentsStor ); 334 } 335 } 336 337 // commit the recovery storage 338 tools::stor::commitStorageIfWriteable( xRecoveryStorage ); 339 } 340 341 //-------------------------------------------------------------------- recoverSubDocuments(const Reference<XStorage> & i_rDocumentStorage,const Reference<XController> & i_rTargetController)342 void DatabaseDocumentRecovery::recoverSubDocuments( const Reference< XStorage >& i_rDocumentStorage, 343 const Reference< XController >& i_rTargetController ) 344 { 345 ENSURE_OR_THROW( i_rDocumentStorage.is(), "illegal document storage" ); 346 Reference< XDatabaseDocumentUI > xDocumentUI( i_rTargetController, UNO_QUERY_THROW ); 347 348 if ( !i_rDocumentStorage->hasByName( lcl_getRecoveryDataSubStorageName() ) ) 349 // that's allowed 350 return; 351 352 // the "recovery" sub storage 353 Reference< XStorage > xRecoveryStorage = i_rDocumentStorage->openStorageElement( lcl_getRecoveryDataSubStorageName(), ElementModes::READ ); 354 355 // read the map from sub storages to object names 356 MapCompTypeToCompDescs aMapCompDescs; 357 SubComponentType aKnownTypes[] = { TABLE, QUERY, FORM, REPORT, RELATION_DESIGN }; 358 for ( size_t i = 0; i < sizeof( aKnownTypes ) / sizeof( aKnownTypes[0] ); ++i ) 359 { 360 if ( !xRecoveryStorage->hasByName( SubComponentRecovery::getComponentsStorageName( aKnownTypes[i] ) ) ) 361 continue; 362 363 Reference< XStorage > xComponentsStor( xRecoveryStorage->openStorageElement( 364 SubComponentRecovery::getComponentsStorageName( aKnownTypes[i] ), ElementModes::READ ) ); 365 lcl_readObjectMap_throw( m_pData->aContext, xComponentsStor, aMapCompDescs[ aKnownTypes[i] ] ); 366 xComponentsStor->dispose(); 367 } 368 369 // recover all sub components as indicated by the map 370 for ( MapCompTypeToCompDescs::const_iterator map = aMapCompDescs.begin(); 371 map != aMapCompDescs.end(); 372 ++map 373 ) 374 { 375 const SubComponentType eComponentType = map->first; 376 377 // the storage for all components of the current type 378 Reference< XStorage > xComponentsStor( xRecoveryStorage->openStorageElement( 379 SubComponentRecovery::getComponentsStorageName( eComponentType ), ElementModes::READ ), UNO_QUERY_THROW ); 380 381 // loop thru all components of this type 382 for ( MapStringToCompDesc::const_iterator stor = map->second.begin(); 383 stor != map->second.end(); 384 ++stor 385 ) 386 { 387 const ::rtl::OUString sComponentName( stor->second.sName ); 388 if ( !xComponentsStor->hasByName( stor->first ) ) 389 { 390 #if OSL_DEBUG_LEVEL > 0 391 ::rtl::OStringBuffer message; 392 message.append( "DatabaseDocumentRecovery::recoverSubDocuments: inconsistent recovery storage: storage '" ); 393 message.append( ::rtl::OUStringToOString( stor->first, RTL_TEXTENCODING_ASCII_US ) ); 394 message.append( "' not found in '" ); 395 message.append( ::rtl::OUStringToOString( SubComponentRecovery::getComponentsStorageName( eComponentType ), RTL_TEXTENCODING_ASCII_US ) ); 396 message.append( "', but required per map file!" ); 397 OSL_ENSURE( false, message.getStr() ); 398 #endif 399 continue; 400 } 401 402 // the controller needs to have a connection to be able to open sub components 403 if ( !xDocumentUI->isConnected() ) 404 xDocumentUI->connect(); 405 406 // recover the single component 407 Reference< XStorage > xCompStor( xComponentsStor->openStorageElement( stor->first, ElementModes::READ ) ); 408 SubComponentRecovery aComponentRecovery( m_pData->aContext, xDocumentUI, eComponentType ); 409 Reference< XComponent > xSubComponent( aComponentRecovery.recoverFromStorage( xCompStor, sComponentName, stor->second.bForEditing ) ); 410 411 // at the moment, we only store, during session save, sub components which are modified. So, set this 412 // recovered sub component to "modified", too. 413 lcl_markModified( xSubComponent ); 414 } 415 416 xComponentsStor->dispose(); 417 } 418 419 xRecoveryStorage->dispose(); 420 421 // now that we successfully recovered, removed the "recovery" sub storage 422 try 423 { 424 i_rDocumentStorage->removeElement( lcl_getRecoveryDataSubStorageName() ); 425 } 426 catch( const Exception& ) 427 { 428 DBG_UNHANDLED_EXCEPTION(); 429 } 430 } 431 432 //........................................................................ 433 } // namespace dbaccess 434 //........................................................................ 435