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