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 // MARKER(update_precomp.py): autogen include statement, do not remove
25 #include "precompiled_svx.hxx"
26 #include "fmscriptingenv.hxx"
27 #include "svx/fmmodel.hxx"
28 
29 /** === begin UNO includes === **/
30 #include <com/sun/star/lang/IllegalArgumentException.hpp>
31 #include <com/sun/star/script/XScriptListener.hpp>
32 #include <com/sun/star/container/XHierarchicalNameAccess.hpp>
33 #include <com/sun/star/reflection/XInterfaceMethodTypeDescription.hpp>
34 #include <com/sun/star/lang/DisposedException.hpp>
35 /** === end UNO includes === **/
36 
37 #include <tools/diagnose_ex.h>
38 #include <cppuhelper/implbase1.hxx>
39 #include <comphelper/implementationreference.hxx>
40 #include <comphelper/componentcontext.hxx>
41 #include <comphelper/processfactory.hxx>
42 #include <vcl/svapp.hxx>
43 #include <vos/mutex.hxx>
44 #include <sfx2/objsh.hxx>
45 #include <sfx2/app.hxx>
46 #include <basic/basmgr.hxx>
47 
48 #include <boost/shared_ptr.hpp>
49 
50 //........................................................................
51 namespace svxform
52 {
53 //........................................................................
54 
55 	/** === begin UNO using === **/
56     using ::com::sun::star::uno::Reference;
57     using ::com::sun::star::script::XEventAttacherManager;
58     using ::com::sun::star::lang::IllegalArgumentException;
59     using ::com::sun::star::script::XScriptListener;
60     using ::com::sun::star::script::ScriptEvent;
61     using ::com::sun::star::uno::RuntimeException;
62     using ::com::sun::star::lang::EventObject;
63     using ::com::sun::star::reflection::InvocationTargetException;
64     using ::com::sun::star::uno::Any;
65     using ::com::sun::star::container::XHierarchicalNameAccess;
66     using ::com::sun::star::reflection::XInterfaceMethodTypeDescription;
67     using ::com::sun::star::uno::UNO_QUERY_THROW;
68     using ::com::sun::star::lang::DisposedException;
69     using ::com::sun::star::uno::RuntimeException;
70     using ::com::sun::star::uno::Exception;
71     using ::com::sun::star::uno::Sequence;
72     using ::com::sun::star::uno::XInterface;
73 	/** === end UNO using === **/
74 
75     class FormScriptingEnvironment;
76 
77     //====================================================================
78 	//= FormScriptListener
79 	//====================================================================
80     typedef ::cppu::WeakImplHelper1 <   XScriptListener
81                                     >   FormScriptListener_Base;
82 
83     /** implements the XScriptListener interface, is used by FormScriptingEnvironment
84     */
85     class FormScriptListener    :public FormScriptListener_Base
86     {
87     private:
88         ::osl::Mutex                                            m_aMutex;
89         ::rtl::Reference< FormScriptingEnvironment >            m_pScriptExecutor;
90 
91     public:
92         FormScriptListener( const ::rtl::Reference< FormScriptingEnvironment >& _pScriptExecutor );
93 
94         // XScriptListener
95         virtual void SAL_CALL firing( const ScriptEvent& aEvent ) throw (RuntimeException);
96         virtual Any SAL_CALL approveFiring( const ScriptEvent& aEvent ) throw (InvocationTargetException, RuntimeException);
97         // XEventListener
98         virtual void SAL_CALL disposing( const EventObject& Source ) throw (RuntimeException);
99 
100         // lifetime control
101         void SAL_CALL dispose();
102 
103     protected:
104         ~FormScriptListener();
105 
106     private:
107         /** determines whether calling a given method at a given listener interface can be done asynchronously
108 
109             @param _rListenerType
110                 the name of the UNO type whose method is to be checked
111             @param _rMethodName
112                 the name of the method at the interface determined by _rListenerType
113 
114             @return
115                 <TRUE/> if and only if the method is declared <code>oneway</code>, i.e. can be called asynchronously
116         */
117         bool    impl_allowAsynchronousCall_nothrow( const ::rtl::OUString& _rListenerType, const ::rtl::OUString& _rMethodName ) const;
118 
119         /** determines whether the instance is already disposed
120         */
impl_isDisposed_nothrow() const121         bool    impl_isDisposed_nothrow() const { return !m_pScriptExecutor.is(); }
122 
123         /** fires the given script event in a thread-safe manner
124 
125             This methods calls our script executor's doFireScriptEvent, with previously releasing the given mutex guard,
126             but ensuring that our script executor is not deleted between this release and the actual call.
127 
128             @param _rGuard
129                 a clearable guard to our mutex. Must be the only active guard to our mutex.
130             @param _rEvent
131                 the event to fire
132             @param _pSyncronousResult
133                 a place to take a possible result of the script call.
134 
135             @precond
136                 m_pScriptExecutor is not <NULL/>.
137         */
138         void    impl_doFireScriptEvent_nothrow( ::osl::ClearableMutexGuard& _rGuard, const ScriptEvent& _rEvent, Any* _pSyncronousResult );
139 
140     private:
141         DECL_LINK( OnAsyncScriptEvent, ScriptEvent* );
142     };
143 
144 	//====================================================================
145 	//= FormScriptingEnvironment
146 	//====================================================================
147     class FormScriptingEnvironment : public IFormScriptingEnvironment
148     {
149     private:
150         typedef ::comphelper::ImplementationReference< FormScriptListener, XScriptListener >    ListenerImplementation;
151 
152     private:
153         ::osl::Mutex            m_aMutex;
154         oslInterlockedCount     m_refCount;
155         ListenerImplementation  m_pScriptListener;
156         FmFormModel&            m_rFormModel;
157         bool                    m_bDisposed;
158 
159     public:
160         FormScriptingEnvironment( FmFormModel& _rModel );
161         virtual ~FormScriptingEnvironment();
162 
163         // callback for FormScriptListener
164         void doFireScriptEvent( const ScriptEvent& _rEvent, Any* _pSyncronousResult );
165 
166         // IFormScriptingEnvironment
167         virtual void registerEventAttacherManager( const Reference< XEventAttacherManager >& _rxManager );
168         virtual void revokeEventAttacherManager( const Reference< XEventAttacherManager >& _rxManager );
169         virtual void dispose();
170 
171         // IReference
172 	    virtual oslInterlockedCount SAL_CALL acquire();
173 	    virtual oslInterlockedCount SAL_CALL release();
174 
175     private:
176         void impl_registerOrRevoke_throw( const Reference< XEventAttacherManager >& _rxManager, bool _bRegister );
177 
178     private:
179         FormScriptingEnvironment();                                                 // never implemented
180         FormScriptingEnvironment( const FormScriptingEnvironment& );                // never implemented
181         FormScriptingEnvironment& operator=( const FormScriptingEnvironment& );     // never implemented
182     };
183 
184 	//====================================================================
185 	//= FormScriptListener
186 	//====================================================================
187     //--------------------------------------------------------------------
FormScriptListener(const::rtl::Reference<FormScriptingEnvironment> & _pScriptExecutor)188     FormScriptListener::FormScriptListener( const ::rtl::Reference< FormScriptingEnvironment >& _pScriptExecutor )
189         :m_pScriptExecutor( _pScriptExecutor )
190     {
191     }
192 
193     //--------------------------------------------------------------------
~FormScriptListener()194     FormScriptListener::~FormScriptListener()
195     {
196     }
197 
198     //--------------------------------------------------------------------
impl_allowAsynchronousCall_nothrow(const::rtl::OUString & _rListenerType,const::rtl::OUString & _rMethodName) const199     bool FormScriptListener::impl_allowAsynchronousCall_nothrow( const ::rtl::OUString& _rListenerType, const ::rtl::OUString& _rMethodName ) const
200     {
201         bool bAllowAsynchronousCall = false;
202         try
203         {
204             ::comphelper::ComponentContext aContext( ::comphelper::getProcessServiceFactory() );
205             Reference< XHierarchicalNameAccess > xTypeDescriptions( aContext.getSingleton( "com.sun.star.reflection.theTypeDescriptionManager" ), UNO_QUERY_THROW );
206 
207             ::rtl::OUString sMethodDescription( _rListenerType );
208             sMethodDescription += ::rtl::OUString::createFromAscii( "::" );
209             sMethodDescription += _rMethodName;
210 
211             Reference< XInterfaceMethodTypeDescription > xMethod( xTypeDescriptions->getByHierarchicalName( sMethodDescription ), UNO_QUERY_THROW );
212             bAllowAsynchronousCall = xMethod->isOneway();
213         }
214         catch( const Exception& )
215         {
216     	    DBG_UNHANDLED_EXCEPTION();
217         }
218         return bAllowAsynchronousCall;
219     }
220 
221     //--------------------------------------------------------------------
impl_doFireScriptEvent_nothrow(::osl::ClearableMutexGuard & _rGuard,const ScriptEvent & _rEvent,Any * _pSyncronousResult)222     void FormScriptListener::impl_doFireScriptEvent_nothrow( ::osl::ClearableMutexGuard& _rGuard, const ScriptEvent& _rEvent, Any* _pSyncronousResult )
223     {
224         OSL_PRECOND( m_pScriptExecutor.is(), "FormScriptListener::impl_doFireScriptEvent_nothrow: this will crash!" );
225 
226         ::rtl::Reference< FormScriptingEnvironment > pExecutor( m_pScriptExecutor );
227         _rGuard.clear();
228         pExecutor->doFireScriptEvent( _rEvent, _pSyncronousResult );
229     }
230 
231     //--------------------------------------------------------------------
firing(const ScriptEvent & _rEvent)232     void SAL_CALL FormScriptListener::firing( const ScriptEvent& _rEvent ) throw (RuntimeException)
233     {
234         ::osl::ClearableMutexGuard aGuard( m_aMutex );
235        static const ::rtl::OUString vbaInterOp =
236            ::rtl::OUString::createFromAscii("VBAInterop");
237        if ( _rEvent.ScriptType.equals(vbaInterOp) )
238            return; // not handled here
239 
240         if ( impl_isDisposed_nothrow() )
241             return;
242 
243         if ( !impl_allowAsynchronousCall_nothrow( _rEvent.ListenerType.getTypeName(), _rEvent.MethodName ) )
244         {
245             impl_doFireScriptEvent_nothrow( aGuard, _rEvent, NULL );
246             return;
247         }
248 
249         acquire();
250         Application::PostUserEvent( LINK( this, FormScriptListener, OnAsyncScriptEvent ), new ScriptEvent( _rEvent ) );
251     }
252 
253     //--------------------------------------------------------------------
approveFiring(const ScriptEvent & _rEvent)254     Any SAL_CALL FormScriptListener::approveFiring( const ScriptEvent& _rEvent ) throw (InvocationTargetException, RuntimeException)
255     {
256         Any aResult;
257 
258         ::osl::ClearableMutexGuard aGuard( m_aMutex );
259         if ( !impl_isDisposed_nothrow() )
260             impl_doFireScriptEvent_nothrow( aGuard, _rEvent, &aResult );
261 
262 	    return aResult;
263     }
264 
265     //--------------------------------------------------------------------
disposing(const EventObject &)266     void SAL_CALL FormScriptListener::disposing( const EventObject& /*Source*/ ) throw (RuntimeException)
267     {
268         // not interested in
269     }
270 
271     //--------------------------------------------------------------------
dispose()272     void SAL_CALL FormScriptListener::dispose()
273     {
274         ::osl::MutexGuard aGuard( m_aMutex );
275         m_pScriptExecutor = NULL;
276     }
277 
278     //--------------------------------------------------------------------
IMPL_LINK(FormScriptListener,OnAsyncScriptEvent,ScriptEvent *,_pEvent)279     IMPL_LINK( FormScriptListener, OnAsyncScriptEvent, ScriptEvent*, _pEvent )
280     {
281         OSL_PRECOND( _pEvent != NULL, "FormScriptListener::OnAsyncScriptEvent: invalid event!" );
282         if ( !_pEvent )
283             return 1L;
284 
285         {
286             ::osl::ClearableMutexGuard aGuard( m_aMutex );
287 
288             if ( !impl_isDisposed_nothrow() )
289                 impl_doFireScriptEvent_nothrow( aGuard, *_pEvent, NULL );
290         }
291 
292         delete _pEvent;
293         // we acquired ourself immediately before posting the event
294         release();
295         return 0L;
296     }
297 
298 	//====================================================================
299 	//= FormScriptingEnvironment
300 	//====================================================================
301     //--------------------------------------------------------------------
FormScriptingEnvironment(FmFormModel & _rModel)302     FormScriptingEnvironment::FormScriptingEnvironment( FmFormModel& _rModel )
303         :m_refCount( 0 )
304         ,m_pScriptListener( NULL )
305         ,m_rFormModel( _rModel )
306         ,m_bDisposed( false )
307     {
308         m_pScriptListener = ListenerImplementation( new FormScriptListener( this ) );
309         // note that this is a cyclic reference between the FormScriptListener and the FormScriptingEnvironment
310         // This cycle is broken up when our instance is disposed.
311     }
312 
313 	//--------------------------------------------------------------------
~FormScriptingEnvironment()314     FormScriptingEnvironment::~FormScriptingEnvironment()
315     {
316     }
317 
318     //--------------------------------------------------------------------
impl_registerOrRevoke_throw(const Reference<XEventAttacherManager> & _rxManager,bool _bRegister)319     void FormScriptingEnvironment::impl_registerOrRevoke_throw( const Reference< XEventAttacherManager >& _rxManager, bool _bRegister )
320     {
321         ::osl::MutexGuard aGuard( m_aMutex );
322 
323         if ( !_rxManager.is() )
324             throw IllegalArgumentException();
325         if ( m_bDisposed )
326             throw DisposedException();
327 
328         try
329         {
330             if ( _bRegister )
331                 _rxManager->addScriptListener( m_pScriptListener.getRef() );
332             else
333                 _rxManager->removeScriptListener( m_pScriptListener.getRef() );
334         }
335         catch( const RuntimeException& ) { throw; }
336         catch( const Exception& )
337         {
338         	DBG_UNHANDLED_EXCEPTION();
339         }
340     }
341 
342     //--------------------------------------------------------------------
registerEventAttacherManager(const Reference<XEventAttacherManager> & _rxManager)343     void FormScriptingEnvironment::registerEventAttacherManager( const Reference< XEventAttacherManager >& _rxManager )
344     {
345         impl_registerOrRevoke_throw( _rxManager, true );
346     }
347 
348     //--------------------------------------------------------------------
revokeEventAttacherManager(const Reference<XEventAttacherManager> & _rxManager)349     void FormScriptingEnvironment::revokeEventAttacherManager( const Reference< XEventAttacherManager >& _rxManager )
350     {
351         impl_registerOrRevoke_throw( _rxManager, false );
352     }
353 
354     //--------------------------------------------------------------------
acquire()355     oslInterlockedCount SAL_CALL FormScriptingEnvironment::acquire()
356     {
357         return osl_incrementInterlockedCount( &m_refCount );
358     }
359 
360     //--------------------------------------------------------------------
release()361     oslInterlockedCount SAL_CALL FormScriptingEnvironment::release()
362     {
363         if ( 0 == osl_decrementInterlockedCount( &m_refCount ) )
364         {
365            delete this;
366            return 0;
367         }
368         return m_refCount;
369     }
370 
371     //--------------------------------------------------------------------
~IFormScriptingEnvironment()372     IFormScriptingEnvironment::~IFormScriptingEnvironment()
373     {
374     }
375 
376     //--------------------------------------------------------------------
377     namespace
378     {
379         //................................................................
380         //. NewStyleUNOScript
381         //................................................................
382         class SAL_NO_VTABLE IScript
383         {
384         public:
385             virtual void invoke( const Sequence< Any >& _rArguments, Any& _rSynchronousResult ) = 0;
386 
~IScript()387             virtual ~IScript() { }
388         };
389         typedef ::boost::shared_ptr< IScript >  PScript;
390 
391         //................................................................
392         //. NewStyleUNOScript
393         //................................................................
394         class NewStyleUNOScript : public IScript
395         {
396             SfxObjectShell&         m_rObjectShell;
397             const ::rtl::OUString   m_sScriptCode;
398 
399         public:
NewStyleUNOScript(SfxObjectShell & _rObjectShell,const::rtl::OUString & _rScriptCode)400             NewStyleUNOScript( SfxObjectShell& _rObjectShell, const ::rtl::OUString& _rScriptCode )
401                 :m_rObjectShell( _rObjectShell )
402                 ,m_sScriptCode( _rScriptCode )
403             {
404             }
405 
406             // IScript
407             virtual void invoke( const Sequence< Any >& _rArguments, Any& _rSynchronousResult );
408         };
409 
410         //................................................................
invoke(const Sequence<Any> & _rArguments,Any & _rSynchronousResult)411         void NewStyleUNOScript::invoke( const Sequence< Any >& _rArguments, Any& _rSynchronousResult )
412         {
413             Sequence< sal_Int16 > aOutArgsIndex;
414             Sequence< Any > aOutArgs;
415 
416             m_rObjectShell.CallXScript( m_sScriptCode, _rArguments, _rSynchronousResult, aOutArgsIndex, aOutArgs );
417         }
418     }
419 
420     //--------------------------------------------------------------------
doFireScriptEvent(const ScriptEvent & _rEvent,Any * _pSyncronousResult)421     void FormScriptingEnvironment::doFireScriptEvent( const ScriptEvent& _rEvent, Any* _pSyncronousResult )
422     {
423 	    ::vos::OClearableGuard aSolarGuard( Application::GetSolarMutex() );
424         ::osl::ClearableMutexGuard aGuard( m_aMutex );
425 
426         if ( m_bDisposed )
427             return;
428 
429         // SfxObjectShellRef is good here since the model controls the lifetime of the object
430         SfxObjectShellRef xObjectShell = m_rFormModel.GetObjectShell();
431 	    if( !xObjectShell.Is() )
432 		    return;
433 
434         // the script to execute
435         PScript pScript;
436 
437         if ( !_rEvent.ScriptType.equalsAscii( "StarBasic" ) )
438         {
439             pScript.reset( new NewStyleUNOScript( *xObjectShell, _rEvent.ScriptCode ) );
440         }
441         else
442         {
443 			::rtl::OUString sScriptCode = _rEvent.ScriptCode;
444 			::rtl::OUString sMacroLocation;
445 
446             // is there a location in the script name ("application" or "document")?
447 			sal_Int32 nPrefixLen = sScriptCode.indexOf( ':' );
448 			DBG_ASSERT( 0 <= nPrefixLen, "FormScriptingEnvironment::doFireScriptEvent: Basic script name in old format encountered!" );
449 
450 			if ( 0 <= nPrefixLen )
451 			{
452 				// and it has such a prefix
453 				sMacroLocation = sScriptCode.copy( 0, nPrefixLen );
454 				DBG_ASSERT( 0 == sMacroLocation.compareToAscii( "document" )
455 						||	0 == sMacroLocation.compareToAscii( "application" ),
456 						"FormScriptingEnvironment::doFireScriptEvent: invalid (unknown) prefix!" );
457 
458 				// strip the prefix: the SfxObjectShell::CallScript knows nothing about such prefixes
459 				sScriptCode = sScriptCode.copy( nPrefixLen + 1 );
460 			}
461 
462             if ( !sMacroLocation.getLength() )
463             {
464                 // legacy format: use the app-wide Basic, if it has a respective method, otherwise fall back to the doc's Basic
465                 if ( SFX_APP()->GetBasicManager()->HasMacro( sScriptCode ) )
466                     sMacroLocation = ::rtl::OUString( RTL_CONSTASCII_USTRINGPARAM( "application" ) );
467                 else
468                     sMacroLocation = ::rtl::OUString( RTL_CONSTASCII_USTRINGPARAM( "document" ) );
469             }
470 
471             ::rtl::OUStringBuffer aScriptURI;
472             aScriptURI.appendAscii( "vnd.sun.star.script:" );
473             aScriptURI.append( sScriptCode );
474             aScriptURI.appendAscii( "?language=Basic" );
475             aScriptURI.appendAscii( "&location=" );
476             aScriptURI.append( sMacroLocation );
477 
478             const ::rtl::OUString sScriptURI( aScriptURI.makeStringAndClear() );
479             pScript.reset( new NewStyleUNOScript( *xObjectShell, sScriptURI ) );
480         }
481 
482         OSL_ENSURE( pScript.get(), "FormScriptingEnvironment::doFireScriptEvent: no script to execute!" );
483         if ( !pScript.get() )
484             // this is an internal error in the above code
485             throw RuntimeException();
486 
487         aGuard.clear();
488 		aSolarGuard.clear();
489 
490         Any aIgnoreResult;
491         pScript->invoke( _rEvent.Arguments, _pSyncronousResult ? *_pSyncronousResult : aIgnoreResult );
492         pScript.reset();
493 
494 	    {
495 	        // object shells are not thread safe, so guard the destruction
496 		    ::vos::OGuard aSolarGuarsReset( Application::GetSolarMutex() );
497 		    xObjectShell = NULL;
498 	    }
499     }
500 
501     //--------------------------------------------------------------------
dispose()502     void FormScriptingEnvironment::dispose()
503     {
504         ::osl::MutexGuard aGuard( m_aMutex );
505         m_bDisposed = true;
506         m_pScriptListener->dispose();
507     }
508 
509 	//--------------------------------------------------------------------
createDefaultFormScriptingEnvironment(FmFormModel & _rModel)510     PFormScriptingEnvironment createDefaultFormScriptingEnvironment( FmFormModel& _rModel )
511     {
512         return new FormScriptingEnvironment( _rModel );
513     }
514 
515 //........................................................................
516 } // namespace svxform
517 //........................................................................
518 
519