LibreOffice Module framework (master) 1
job.cxx
Go to the documentation of this file.
1/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2/*
3 * This file is part of the LibreOffice project.
4 *
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/.
8 *
9 * This file incorporates work covered by the following license notice:
10 *
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 .
18 */
19
20#include <jobs/job.hxx>
21#include <jobs/jobresult.hxx>
22
23#include <com/sun/star/frame/Desktop.hpp>
24#include <com/sun/star/frame/TerminationVetoException.hpp>
25#include <com/sun/star/task/XJob.hpp>
26#include <com/sun/star/task/XAsyncJob.hpp>
27#include <com/sun/star/util/CloseVetoException.hpp>
28#include <com/sun/star/util/XCloseBroadcaster.hpp>
29#include <com/sun/star/util/XCloseable.hpp>
30#include <com/sun/star/lang/DisposedException.hpp>
31
33#include <sal/log.hxx>
35#include <utility>
36#include <vcl/svapp.hxx>
37
38namespace framework{
39
53Job::Job( /*IN*/ const css::uno::Reference< css::uno::XComponentContext >& xContext ,
54 /*IN*/ css::uno::Reference< css::frame::XFrame > xFrame )
55 : m_aJobCfg (xContext )
56 , m_xContext (xContext )
57 , m_xFrame (std::move(xFrame ))
58 , m_bListenOnDesktop (false )
59 , m_bListenOnFrame (false )
60 , m_bListenOnModel (false )
61 , m_bPendingCloseFrame (false )
62 , m_bPendingCloseModel (false )
63 , m_eRunState (E_NEW )
64{
65}
66
80Job::Job( /*IN*/ const css::uno::Reference< css::uno::XComponentContext >& xContext ,
81 /*IN*/ css::uno::Reference< css::frame::XModel > xModel )
82 : m_aJobCfg (xContext )
83 , m_xContext (xContext )
84 , m_xModel (std::move(xModel ))
85 , m_bListenOnDesktop (false )
86 , m_bListenOnFrame (false )
87 , m_bListenOnModel (false )
88 , m_bPendingCloseFrame (false )
89 , m_bPendingCloseModel (false )
90 , m_eRunState (E_NEW )
91{
92}
93
99Job::~Job()
100{
101}
102
116void Job::setDispatchResultFake( /*IN*/ const css::uno::Reference< css::frame::XDispatchResultListener >& xListener ,
117 /*IN*/ const css::uno::Reference< css::uno::XInterface >& xSourceFake )
118{
120
121 // reject dangerous calls
122 if (m_eRunState != E_NEW)
123 {
124 SAL_INFO("fwk", "Job::setJobData(): job may still running or already finished");
125 return;
126 }
127
128 m_xResultListener = xListener;
129 m_xResultSourceFake = xSourceFake;
130}
131
132void Job::setJobData( const JobData& aData )
133{
135
136 // reject dangerous calls
137 if (m_eRunState != E_NEW)
138 {
139 SAL_INFO("fwk", "Job::setJobData(): job may still running or already finished");
140 return;
141 }
142
143 m_aJobCfg = aData;
144}
145
157void Job::execute( /*IN*/ const css::uno::Sequence< css::beans::NamedValue >& lDynamicArgs )
158{
159 /* SAFE { */
160 class SolarMutexAntiGuard {
161 SolarMutexResettableGuard & m_rGuard;
162 public:
163 SolarMutexAntiGuard(SolarMutexResettableGuard & rGuard) : m_rGuard(rGuard)
164 {
165 m_rGuard.clear();
166 }
167 ~SolarMutexAntiGuard()
168 {
169 m_rGuard.reset();
170 }
171 };
172 SolarMutexResettableGuard aWriteLock;
173
174 // reject dangerous calls
175 if (m_eRunState != E_NEW)
176 {
177 SAL_INFO("fwk", "Job::execute(): job may still running or already finished");
178 return;
179 }
180
181 // create the environment and mark this job as running ...
182 m_eRunState = E_RUNNING;
183 impl_startListening();
184
185 css::uno::Reference< css::task::XAsyncJob > xAJob;
186 css::uno::Reference< css::task::XJob > xSJob;
187 css::uno::Sequence< css::beans::NamedValue > lJobArgs = impl_generateJobArgs(lDynamicArgs);
188
189 // It's necessary to hold us self alive!
190 // Otherwise we might die by ref count ...
191 css::uno::Reference< css::task::XJobListener > xThis(this);
192
193 try
194 {
195 // create the job
196 // We must check for the supported interface on demand!
197 // But we prefer the synchronous one ...
198 m_xJob = m_xContext->getServiceManager()->createInstanceWithContext(m_aJobCfg.getService(), m_xContext);
199 xSJob.set(m_xJob, css::uno::UNO_QUERY);
200 if (!xSJob.is())
201 xAJob.set(m_xJob, css::uno::UNO_QUERY);
202
203 // execute it asynchronous
204 if (xAJob.is())
205 {
206 m_aAsyncWait.reset();
207 SolarMutexAntiGuard const ag(aWriteLock);
208 /* } SAFE */
209 xAJob->executeAsync(lJobArgs, xThis);
210 // wait for finishing this job - so this method
211 // does the same for synchronous and asynchronous jobs!
212 m_aAsyncWait.wait();
213 /* SAFE { */
214 // Note: Result handling was already done inside the callback!
215 }
216 // execute it synchron
217 else if (xSJob.is())
218 {
219 css::uno::Any aResult;
220 {
221 SolarMutexAntiGuard const ag(aWriteLock);
222 /* } SAFE */
223 aResult = xSJob->execute(lJobArgs);
224 }
225 /* SAFE { */
226 impl_reactForJobResult(aResult);
227 }
228 }
229 #if OSL_DEBUG_LEVEL > 0
230 catch(const css::uno::Exception&)
231 {
232 TOOLS_INFO_EXCEPTION("fwk", "Job::execute(): Got exception during job execution");
233 }
234 #else
235 catch(const css::uno::Exception&)
236 {}
237 #endif
238
239 // deinitialize the environment and mark this job as finished...
240 // but don't overwrite any information about STOPPED or might DISPOSED jobs!
241 impl_stopListening();
242 if (m_eRunState == E_RUNNING)
243 m_eRunState = E_STOPPED_OR_FINISHED;
244
245 // If we got a close request from our frame or model...
246 // but we disagreed with that by throwing a veto exception...
247 // and got the ownership...
248 // we have to close the resource frame or model now -
249 // and to disable ourself!
250 if (m_bPendingCloseFrame)
251 {
252 m_bPendingCloseFrame = false;
253 css::uno::Reference< css::util::XCloseable > xClose(m_xFrame, css::uno::UNO_QUERY);
254 if (xClose.is())
255 {
256 try
257 {
258 xClose->close(true);
259 }
260 catch(const css::util::CloseVetoException&) {}
261 }
262 }
263
264 if (m_bPendingCloseModel)
265 {
266 m_bPendingCloseModel = false;
267 css::uno::Reference< css::util::XCloseable > xClose(m_xModel, css::uno::UNO_QUERY);
268 if (xClose.is())
269 {
270 try
271 {
272 xClose->close(true);
273 }
274 catch(const css::util::CloseVetoException&) {}
275 }
276 }
277
278 aWriteLock.clear();
279 /* SAFE { */
280
281 // release this instance ...
282 die();
283}
284
293void Job::die()
294{
296
297 impl_stopListening();
298
299 if (m_eRunState != E_DISPOSED)
300 {
301 try
302 {
303 css::uno::Reference< css::lang::XComponent > xDispose(m_xJob, css::uno::UNO_QUERY);
304 if (xDispose.is())
305 {
306 xDispose->dispose();
307 m_eRunState = E_DISPOSED;
308 }
309 }
310 catch(const css::lang::DisposedException&)
311 {
312 m_eRunState = E_DISPOSED;
313 }
314 }
315
316 m_xJob.clear();
317 m_xFrame.clear();
318 m_xModel.clear();
319 m_xDesktop.clear();
320 m_xResultListener.clear();
321 m_xResultSourceFake.clear();
322 m_bPendingCloseFrame = false;
323 m_bPendingCloseModel = false;
324}
325
341css::uno::Sequence< css::beans::NamedValue > Job::impl_generateJobArgs( /*IN*/ const css::uno::Sequence< css::beans::NamedValue >& lDynamicArgs )
342{
343 css::uno::Sequence< css::beans::NamedValue > lAllArgs;
344
345 /* SAFE { */
346 SolarMutexClearableGuard aReadLock;
347
348 // the real structure of the returned list depends from the environment of this job!
349 JobData::EMode eMode = m_aJobCfg.getMode();
350
351 // Create list of environment variables. This list must be part of the
352 // returned structure every time... but some of its members are optional!
353 sal_Int32 nLen = 1;
354 if (m_xFrame.is())
355 ++nLen;
356 if (m_xModel.is())
357 ++nLen;
358 if (eMode==JobData::E_EVENT)
359 ++nLen;
360 css::uno::Sequence< css::beans::NamedValue > lEnvArgs(nLen);
361 auto plEnvArgs = lEnvArgs.getArray();
362 plEnvArgs[0].Name = "EnvType";
363 plEnvArgs[0].Value <<= m_aJobCfg.getEnvironmentDescriptor();
364
365 sal_Int32 i = 0;
366 if (m_xFrame.is())
367 {
368 ++i;
369 plEnvArgs[i].Name = "Frame";
370 plEnvArgs[i].Value <<= m_xFrame;
371 }
372 if (m_xModel.is())
373 {
374 ++i;
375 plEnvArgs[i].Name = "Model";
376 plEnvArgs[i].Value <<= m_xModel;
377 }
378 if (eMode==JobData::E_EVENT)
379 {
380 ++i;
381 plEnvArgs[i].Name = "EventName";
382 plEnvArgs[i].Value <<= m_aJobCfg.getEvent();
383 }
384
385 // get the configuration data from the job data container ... if possible
386 // Means: if this job has any configuration data. Note: only really
387 // filled lists will be set to the return structure at the end of this method.
388 css::uno::Sequence< css::beans::NamedValue > lConfigArgs;
389 std::vector< css::beans::NamedValue > lJobConfigArgs;
390 if (eMode==JobData::E_ALIAS || eMode==JobData::E_EVENT)
391 {
392 lConfigArgs = m_aJobCfg.getConfig();
393 lJobConfigArgs = m_aJobCfg.getJobConfig();
394 }
395
396 aReadLock.clear();
397 /* } SAFE */
398
399 // Add all valid (not empty) lists to the return list
400 if (lConfigArgs.hasElements())
401 {
402 sal_Int32 nLength = lAllArgs.getLength();
403 lAllArgs.realloc(nLength+1);
404 auto plAllArgs = lAllArgs.getArray();
405 plAllArgs[nLength].Name = "Config";
406 plAllArgs[nLength].Value <<= lConfigArgs;
407 }
408 if (!lJobConfigArgs.empty())
409 {
410 sal_Int32 nLength = lAllArgs.getLength();
411 lAllArgs.realloc(nLength+1);
412 auto plAllArgs = lAllArgs.getArray();
413 plAllArgs[nLength].Name = "JobConfig";
414 plAllArgs[nLength].Value <<= comphelper::containerToSequence(lJobConfigArgs);
415 }
416 if (lEnvArgs.hasElements())
417 {
418 sal_Int32 nLength = lAllArgs.getLength();
419 lAllArgs.realloc(nLength+1);
420 auto plAllArgs = lAllArgs.getArray();
421 plAllArgs[nLength].Name = "Environment";
422 plAllArgs[nLength].Value <<= lEnvArgs;
423 }
424 if (lDynamicArgs.hasElements())
425 {
426 sal_Int32 nLength = lAllArgs.getLength();
427 lAllArgs.realloc(nLength+1);
428 auto plAllArgs = lAllArgs.getArray();
429 plAllArgs[nLength].Name = "DynamicData";
430 plAllArgs[nLength].Value <<= lDynamicArgs;
431 }
432
433 return lAllArgs;
434}
435
447void Job::impl_reactForJobResult( /*IN*/ const css::uno::Any& aResult )
448{
450
451 // analyze the result set ...
452 JobResult aAnalyzedResult(aResult);
453
454 // some of the following operations will be supported for different environments
455 // or different type of jobs only.
456 JobData::EEnvironment eEnvironment = m_aJobCfg.getEnvironment();
457
458 // write back the job specific configuration data ...
459 // If the environment allow it and if this job has a configuration!
460 if (
461 (m_aJobCfg.hasConfig() ) &&
462 (aAnalyzedResult.existPart(JobResult::E_ARGUMENTS))
463 )
464 {
465 m_aJobCfg.setJobConfig(aAnalyzedResult.getArguments());
466 }
467
468 // disable a job for further executions.
469 // Note: this option is available inside the environment EXECUTOR only
470 if (
471// (eEnvironment == JobData::E_EXECUTION ) &&
472 (m_aJobCfg.hasConfig() ) &&
473 (aAnalyzedResult.existPart(JobResult::E_DEACTIVATE))
474 )
475 {
476 m_aJobCfg.disableJob();
477 }
478
479 // notify any interested listener with the may given result state.
480 // Note: this option is available inside the environment DISPATCH only
481 if (
482 (eEnvironment == JobData::E_DISPATCH ) &&
483 (m_xResultListener.is() ) &&
484 (aAnalyzedResult.existPart(JobResult::E_DISPATCHRESULT))
485 )
486 {
487 // Attention: Because the listener expect that the original object send this event ...
488 // and we nor the job are the right ones ...
489 // our user has set itself before. So we can fake this source address!
490 css::frame::DispatchResultEvent aEvent = aAnalyzedResult.getDispatchResult();
491 aEvent.Source = m_xResultSourceFake;
492 m_xResultListener->dispatchFinished(aEvent);
493 }
494}
495
512void Job::impl_startListening()
513{
515
516 // listening for office shutdown
517 if (!m_xDesktop.is() && !m_bListenOnDesktop)
518 {
519 try
520 {
521 m_xDesktop = css::frame::Desktop::create( m_xContext );
522 css::uno::Reference< css::frame::XTerminateListener > xThis(this);
523 m_xDesktop->addTerminateListener(xThis);
524 m_bListenOnDesktop = true;
525 }
526 catch(const css::uno::Exception&)
527 {
528 m_xDesktop.clear();
529 }
530 }
531
532 // listening for frame closing
533 if (m_xFrame.is() && !m_bListenOnFrame)
534 {
535 try
536 {
537 css::uno::Reference< css::util::XCloseBroadcaster > xCloseable(m_xFrame , css::uno::UNO_QUERY);
538 css::uno::Reference< css::util::XCloseListener > xThis(this);
539 if (xCloseable.is())
540 {
541 xCloseable->addCloseListener(xThis);
542 m_bListenOnFrame = true;
543 }
544 }
545 catch(const css::uno::Exception&)
546 {
547 m_bListenOnFrame = false;
548 }
549 }
550
551 // listening for model closing
552 if (!m_xModel.is() || m_bListenOnModel)
553 return;
554
555 try
556 {
557 css::uno::Reference< css::util::XCloseBroadcaster > xCloseable(m_xModel , css::uno::UNO_QUERY);
558 css::uno::Reference< css::util::XCloseListener > xThis(this);
559 if (xCloseable.is())
560 {
561 xCloseable->addCloseListener(xThis);
562 m_bListenOnModel = true;
563 }
564 }
565 catch(const css::uno::Exception&)
566 {
567 m_bListenOnModel = false;
568 }
569}
570
575void Job::impl_stopListening()
576{
578
579 // stop listening for office shutdown
580 if (m_xDesktop.is() && m_bListenOnDesktop)
581 {
582 try
583 {
584 css::uno::Reference< css::frame::XTerminateListener > xThis(this);
585 m_xDesktop->removeTerminateListener(xThis);
586 m_xDesktop.clear();
587 m_bListenOnDesktop = false;
588 }
589 catch(const css::uno::Exception&)
590 {
591 }
592 }
593
594 // stop listening for frame closing
595 if (m_xFrame.is() && m_bListenOnFrame)
596 {
597 try
598 {
599 css::uno::Reference< css::util::XCloseBroadcaster > xCloseable(m_xFrame , css::uno::UNO_QUERY);
600 css::uno::Reference< css::util::XCloseListener > xThis(this);
601 if (xCloseable.is())
602 {
603 xCloseable->removeCloseListener(xThis);
604 m_bListenOnFrame = false;
605 }
606 }
607 catch(const css::uno::Exception&)
608 {
609 }
610 }
611
612 // stop listening for model closing
613 if (!(m_xModel.is() && m_bListenOnModel))
614 return;
615
616 try
617 {
618 css::uno::Reference< css::util::XCloseBroadcaster > xCloseable(m_xModel , css::uno::UNO_QUERY);
619 css::uno::Reference< css::util::XCloseListener > xThis(this);
620 if (xCloseable.is())
621 {
622 xCloseable->removeCloseListener(xThis);
623 m_bListenOnModel = false;
624 }
625 }
626 catch(const css::uno::Exception&)
627 {
628 }
629}
630
645void SAL_CALL Job::jobFinished( /*IN*/ const css::uno::Reference< css::task::XAsyncJob >& xJob ,
646 /*IN*/ const css::uno::Any& aResult )
647{
649
650 // It's necessary to check this.
651 // May this job was cancelled by any other reason
652 // some milliseconds before. :-)
653 if (m_xJob.is() && m_xJob==xJob)
654 {
655 // react for his results
656 // (means enable/disable it for further requests
657 // or save arguments or notify listener ...)
658 impl_reactForJobResult(aResult);
659
660 // Let the job die!
661 m_xJob.clear();
662 }
663
664 // And let the start method "execute()" finishing it's job.
665 // But do it every time. So any outside blocking code can finish
666 // his work too.
667 m_aAsyncWait.set();
668}
669
684void SAL_CALL Job::queryTermination( /*IN*/ const css::lang::EventObject& )
685{
687
688 // Otherwise try to close() it
689 css::uno::Reference< css::util::XCloseable > xClose(m_xJob, css::uno::UNO_QUERY);
690 if (xClose.is())
691 {
692 try
693 {
694 xClose->close(false);
695 m_eRunState = E_STOPPED_OR_FINISHED;
696 }
697 catch(const css::util::CloseVetoException&) {}
698 }
699
700 if (m_eRunState != E_STOPPED_OR_FINISHED)
701 {
702 css::uno::Reference< css::uno::XInterface > xThis(static_cast< ::cppu::OWeakObject* >(this), css::uno::UNO_QUERY);
703 throw css::frame::TerminationVetoException("job still in progress", xThis);
704 }
705}
706
720void SAL_CALL Job::notifyTermination( /*IN*/ const css::lang::EventObject& )
721{
722 die();
723 // Do nothing else here. Our internal resources was released ...
724}
725
744void SAL_CALL Job::queryClosing( const css::lang::EventObject& aEvent ,
745 sal_Bool bGetsOwnership )
746{
748
749 // do nothing, if no internal job is still running ...
750 // The frame or model can be closed then successfully.
751 if (m_eRunState != E_RUNNING)
752 return;
753
754 // try close() first at the job.
755 // The job can agree or disagree with this request.
756 css::uno::Reference< css::util::XCloseable > xClose(m_xJob, css::uno::UNO_QUERY);
757 if (xClose.is())
758 {
759 xClose->close(bGetsOwnership);
760 // Here we can say: "this job was stopped successfully". Because
761 // no veto exception was thrown!
762 m_eRunState = E_STOPPED_OR_FINISHED;
763 return;
764 }
765
766 // try dispose() then
767 // Here the job has no chance for a veto.
768 // But we must be aware of an "already disposed exception"...
769 try
770 {
771 css::uno::Reference< css::lang::XComponent > xDispose(m_xJob, css::uno::UNO_QUERY);
772 if (xDispose.is())
773 {
774 xDispose->dispose();
775 m_eRunState = E_DISPOSED;
776 }
777 }
778 catch(const css::lang::DisposedException&)
779 {
780 // the job was already disposed by any other mechanism !?
781 // But it's not interesting for us. For us this job is stopped now.
782 m_eRunState = E_DISPOSED;
783 }
784
785 if (m_eRunState != E_DISPOSED)
786 {
787 // analyze event source - to find out, which resource called queryClosing() at this
788 // job wrapper. We must bind a "pending close" request to this resource.
789 // Closing of the corresponding resource will be done if our internal job finish it's work.
790 m_bPendingCloseFrame = (m_xFrame.is() && aEvent.Source == m_xFrame);
791 m_bPendingCloseModel = (m_xModel.is() && aEvent.Source == m_xModel);
792
793 // throw suitable veto exception - because the internal job could not be cancelled.
794 css::uno::Reference< css::uno::XInterface > xThis(static_cast< ::cppu::OWeakObject* >(this), css::uno::UNO_QUERY);
795 throw css::util::CloseVetoException("job still in progress", xThis);
796 }
797
798 // No veto ...
799 // But don't call die() here or free our internal member.
800 // This must be done inside notifyClosing() only. Otherwise the
801 // might stopped job has no chance to return its results or
802 // call us back. We must give him the chance to finish it's work successfully.
803}
804
813void SAL_CALL Job::notifyClosing( const css::lang::EventObject& )
814{
815 die();
816 // Do nothing else here. Our internal resources was released ...
817}
818
827void SAL_CALL Job::disposing( const css::lang::EventObject& aEvent )
828{
829 /* SAFE { */
830 {
831 SolarMutexGuard aWriteLock;
832
833 if (m_xDesktop.is() && aEvent.Source == m_xDesktop)
834 {
835 m_xDesktop.clear();
836 m_bListenOnDesktop = false;
837 }
838 else if (m_xFrame.is() && aEvent.Source == m_xFrame)
839 {
840 m_xFrame.clear();
841 m_bListenOnFrame = false;
842 }
843 else if (m_xModel.is() && aEvent.Source == m_xModel)
844 {
845 m_xModel.clear();
846 m_bListenOnModel = false;
847 }
848 }
849 /* } SAFE */
850
851 die();
852 // Do nothing else here. Our internal resources was released ...
853}
854
855} // namespace framework
856
857/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
css::uno::Reference< css::lang::XComponent > m_xFrame
AnyEventRef aEvent
Job(const css::uno::Reference< css::uno::XComponentContext > &xContext, css::uno::Reference< css::frame::XFrame > xFrame)
Reference< XDesktop2 > m_xDesktop
#define TOOLS_INFO_EXCEPTION(area, stream)
Reference< frame::XModel > m_xModel
css::uno::Reference< css::uno::XComponentContext > m_xContext
Mode eMode
#define SAL_INFO(area, stream)
constexpr OUStringLiteral aData
css::uno::Sequence< DstElementType > containerToSequence(const SrcType &i_Container)
int i
Reference< XFrame > xFrame
Reference< XModel > xModel
unsigned char sal_Bool
sal_Int32 nLength