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         // .........................................................................
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         // .........................................................................
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         // .........................................................................
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         // .........................................................................
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         // .........................................................................
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         // .........................................................................
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         // .........................................................................
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         // .........................................................................
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         // .........................................................................
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         // .........................................................................
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 
266         DatabaseDocumentRecovery_Data( const ::comphelper::ComponentContext& i_rContext )
267             :aContext( i_rContext )
268         {
269         }
270     };
271 
272 	//====================================================================
273 	//= DatabaseDocumentRecovery
274 	//====================================================================
275 	//--------------------------------------------------------------------
276     DatabaseDocumentRecovery::DatabaseDocumentRecovery( const ::comphelper::ComponentContext& i_rContext )
277         :m_pData( new DatabaseDocumentRecovery_Data( i_rContext ) )
278     {
279     }
280 
281 	//--------------------------------------------------------------------
282     DatabaseDocumentRecovery::~DatabaseDocumentRecovery()
283     {
284     }
285 
286 	//--------------------------------------------------------------------
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     //--------------------------------------------------------------------
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.makeStringAndClear() );
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