Coverage Report

Created: 2025-11-16 09:57

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/libreoffice/framework/source/jobs/jobexecutor.cxx
Line
Count
Source
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/configaccess.hxx>
22
#include <classes/converter.hxx>
23
24
#include <helper/mischelper.hxx>
25
26
#include <com/sun/star/container/XNameAccess.hpp>
27
#include <com/sun/star/container/XContainer.hpp>
28
#include <com/sun/star/frame/ModuleManager.hpp>
29
#include <com/sun/star/task/XJobExecutor.hpp>
30
#include <com/sun/star/container/XContainerListener.hpp>
31
#include <com/sun/star/lang/XServiceInfo.hpp>
32
#include <com/sun/star/document/XEventListener.hpp>
33
34
#include <comphelper/compbase.hxx>
35
#include <cppuhelper/supportsservice.hxx>
36
#include <comphelper/configuration.hxx>
37
#include <unotools/configpaths.hxx>
38
#include <rtl/ref.hxx>
39
#include <sal/log.hxx>
40
#include <vcl/svapp.hxx>
41
42
using namespace framework;
43
44
namespace {
45
46
typedef comphelper::WeakComponentImplHelper<
47
          css::lang::XServiceInfo
48
        , css::task::XJobExecutor
49
        , css::container::XContainerListener // => lang.XEventListener
50
        , css::document::XEventListener >
51
    Base;
52
53
/**
54
    @short  implements a job executor, which can be triggered from any code
55
    @descr  It uses the given trigger event to locate any registered job service
56
            inside the configuration and execute it. Of course it controls the
57
            lifetime of such jobs too.
58
 */
59
class JobExecutor : public Base
60
{
61
private:
62
63
    /** reference to the uno service manager */
64
    css::uno::Reference< css::uno::XComponentContext > m_xContext;
65
66
    /** cached list of all registered event names of cfg for call optimization. */
67
    std::vector<OUString> m_lEvents;
68
69
    /** we listen at the configuration for changes at the event list. */
70
    ConfigAccess m_aConfig;
71
72
    /** helper to allow us listen to the configuration without a cyclic dependency */
73
    rtl::Reference<WeakContainerListener> m_xConfigListener;
74
75
    virtual void disposing(std::unique_lock<std::mutex>& rGuard) final override;
76
77
public:
78
79
    explicit JobExecutor(const css::uno::Reference< css::uno::XComponentContext >& xContext);
80
    virtual ~JobExecutor() override;
81
82
    virtual OUString SAL_CALL getImplementationName() override
83
0
    {
84
0
        return u"com.sun.star.comp.framework.JobExecutor"_ustr;
85
0
    }
86
87
    virtual sal_Bool SAL_CALL supportsService(OUString const & ServiceName) override
88
0
    {
89
0
        return cppu::supportsService(this, ServiceName);
90
0
    }
91
92
    virtual css::uno::Sequence<OUString> SAL_CALL getSupportedServiceNames() override
93
0
    {
94
0
        return {u"com.sun.star.task.JobExecutor"_ustr};
95
0
    }
96
97
    // task.XJobExecutor
98
    virtual void SAL_CALL trigger( const OUString& sEvent ) override;
99
100
    /// Initialization function after having acquire()'d.
101
    void initListeners();
102
103
    // document.XEventListener
104
    virtual void SAL_CALL notifyEvent( const css::document::EventObject& aEvent ) override;
105
106
    // container.XContainerListener
107
    virtual void SAL_CALL elementInserted( const css::container::ContainerEvent& aEvent ) override;
108
    virtual void SAL_CALL elementRemoved ( const css::container::ContainerEvent& aEvent ) override;
109
    virtual void SAL_CALL elementReplaced( const css::container::ContainerEvent& aEvent ) override;
110
111
    // lang.XEventListener
112
    virtual void SAL_CALL disposing( const css::lang::EventObject& aEvent ) override;
113
};
114
115
/**
116
    @short      standard ctor
117
    @descr      It initialize this new instance.
118
119
    @param      xContext
120
                    reference to the uno service manager
121
 */
122
JobExecutor::JobExecutor( /*IN*/ const css::uno::Reference< css::uno::XComponentContext >& xContext )
123
3
    : m_xContext          (xContext                                                        )
124
3
    , m_aConfig           (xContext, u"/org.openoffice.Office.Jobs/Events"_ustr)
125
3
{
126
3
}
127
128
void JobExecutor::initListeners()
129
3
{
130
3
    if (comphelper::IsFuzzing())
131
3
        return;
132
133
    // read the list of all currently registered events inside configuration.
134
    // e.g. "/org.openoffice.Office.Jobs/Events/<event name>"
135
    // We need it later to check if an incoming event request can be executed successfully
136
    // or must be rejected. It's an optimization! Of course we must implement updating of this
137
    // list too ... Be listener at the configuration.
138
139
0
    m_aConfig.open(ConfigAccess::E_READONLY);
140
0
    if (m_aConfig.getMode() != ConfigAccess::E_READONLY)
141
0
        return;
142
143
0
    css::uno::Reference< css::container::XNameAccess > xRegistry(
144
0
            m_aConfig.cfg(), css::uno::UNO_QUERY);
145
0
    if (xRegistry.is())
146
0
        m_lEvents = Converter::convert_seqOUString2OUStringList(
147
0
                xRegistry->getElementNames());
148
149
0
    css::uno::Reference< css::container::XContainer > xNotifier(
150
0
            m_aConfig.cfg(), css::uno::UNO_QUERY);
151
0
    if (xNotifier.is())
152
0
    {
153
0
        m_xConfigListener = new WeakContainerListener(this);
154
0
        xNotifier->addContainerListener(m_xConfigListener);
155
0
    }
156
157
    // don't close cfg here!
158
    // It will be done inside disposing ...
159
0
}
160
161
JobExecutor::~JobExecutor()
162
0
{
163
0
    std::unique_lock g(m_aMutex);
164
0
    disposing(g);
165
0
}
166
167
0
void JobExecutor::disposing(std::unique_lock<std::mutex>& /*rGuard*/) {
168
0
    css::uno::Reference<css::container::XContainer> notifier;
169
0
    rtl::Reference<WeakContainerListener> listener;
170
0
    if (m_aConfig.getMode() != ConfigAccess::E_CLOSED) {
171
0
        notifier.set(m_aConfig.cfg(), css::uno::UNO_QUERY);
172
0
        listener = m_xConfigListener;
173
0
        m_aConfig.close();
174
0
    }
175
0
    m_xConfigListener.clear();
176
0
    if (notifier.is()) {
177
0
        notifier->removeContainerListener(listener);
178
0
    }
179
0
}
180
181
/**
182
    @short  implementation of XJobExecutor interface
183
    @descr  We use the given event to locate any registered job inside our configuration
184
            and execute it. Further we control the lifetime of it and suppress
185
            shutdown of the office till all jobs was finished.
186
187
    @param  sEvent
188
                is used to locate registered jobs
189
 */
190
void SAL_CALL JobExecutor::trigger( const OUString& sEvent )
191
0
{
192
0
    SAL_INFO( "fwk", "JobExecutor::trigger()");
193
194
    /* SAFE */
195
0
    {
196
0
        std::unique_lock g(m_aMutex);
197
198
        // Optimization!
199
        // Check if the given event name exist inside configuration and reject wrong requests.
200
        // This optimization suppress using of the cfg api for getting event and job descriptions ...
201
0
        if (std::find(m_lEvents.begin(), m_lEvents.end(), sEvent) == m_lEvents.end())
202
0
            return;
203
204
0
    } /* SAFE */
205
206
    // get list of all enabled jobs
207
    // The called static helper methods read it from the configuration and
208
    // filter disabled jobs using it's time stamp values.
209
0
    std::vector< OUString > lJobs = JobData::getEnabledJobsForEvent(m_xContext, sEvent);
210
211
    // step over all enabled jobs and execute it
212
0
    size_t c = lJobs.size();
213
0
    for (size_t j=0; j<c; ++j)
214
0
    {
215
0
        JobData aCfg(m_xContext);
216
0
        aCfg.setEvent(sEvent, lJobs[j]);
217
0
        aCfg.setEnvironment(JobData::E_EXECUTION);
218
219
        /*Attention!
220
            Jobs implements interfaces and dies by ref count!
221
            And freeing of such uno object is done by uno itself.
222
            So we have to use dynamic memory everytimes.
223
         */
224
0
        rtl::Reference<Job> pJob = new Job(m_xContext, css::uno::Reference< css::frame::XFrame >());
225
0
        pJob->setJobData(aCfg);
226
227
0
        pJob->execute(css::uno::Sequence< css::beans::NamedValue >());
228
0
    }
229
0
}
230
231
void SAL_CALL JobExecutor::notifyEvent( const css::document::EventObject& aEvent )
232
0
{
233
0
    static constexpr OUString EVENT_ON_DOCUMENT_OPENED(u"onDocumentOpened"_ustr);   // Job UI  event : OnNew    or OnLoad
234
0
    static constexpr OUString EVENT_ON_DOCUMENT_ADDED(u"onDocumentAdded"_ustr);     // Job API event : OnCreate or OnLoadFinished
235
236
0
    OUString aModuleIdentifier;
237
0
    ::std::vector< JobData::TJob2DocEventBinding > lJobs;
238
239
    // Optimization!
240
    // Check if the given event name exist inside configuration and reject wrong requests.
241
    // This optimization suppress using of the cfg api for getting event and job descriptions.
242
    // see using of m_lEvents.find() below ...
243
244
    // retrieve event context from event source
245
0
    try
246
0
    {
247
0
        aModuleIdentifier = css::frame::ModuleManager::create( m_xContext )->identify( aEvent.Source );
248
0
    }
249
0
    catch( const css::uno::Exception& )
250
0
    {}
251
252
    /* SAFE */
253
0
    {
254
0
        std::unique_lock g(m_aMutex);
255
256
        // Special feature: If the events "OnNew" or "OnLoad" occurs - we generate our own event "onDocumentOpened".
257
0
        if (
258
0
            (aEvent.EventName == "OnNew") ||
259
0
            (aEvent.EventName == "OnLoad")
260
0
           )
261
0
        {
262
0
            if (std::find(m_lEvents.begin(), m_lEvents.end(), EVENT_ON_DOCUMENT_OPENED) != m_lEvents.end())
263
0
                JobData::appendEnabledJobsForEvent(m_xContext, EVENT_ON_DOCUMENT_OPENED, lJobs);
264
0
        }
265
266
        // Special feature: If the events "OnCreate" or "OnLoadFinished" occurs - we generate our own event "onDocumentAdded".
267
0
        if (
268
0
            (aEvent.EventName == "OnCreate") ||
269
0
            (aEvent.EventName == "OnLoadFinished")
270
0
           )
271
0
        {
272
0
            if (std::find(m_lEvents.begin(), m_lEvents.end(), EVENT_ON_DOCUMENT_ADDED) != m_lEvents.end())
273
0
                JobData::appendEnabledJobsForEvent(m_xContext, EVENT_ON_DOCUMENT_ADDED, lJobs);
274
0
        }
275
276
        // Add all jobs for "real" notified event too .-)
277
0
        if (std::find(m_lEvents.begin(), m_lEvents.end(), aEvent.EventName) != m_lEvents.end())
278
0
            JobData::appendEnabledJobsForEvent(m_xContext, aEvent.EventName, lJobs);
279
0
    } /* SAFE */
280
281
    // step over all enabled jobs and execute it
282
0
    for (auto const& lJob : lJobs)
283
0
    {
284
0
        rtl::Reference<Job> pJob;
285
286
0
        const JobData::TJob2DocEventBinding& rBinding = lJob;
287
288
0
        JobData aCfg(m_xContext);
289
0
        aCfg.setEvent(rBinding.m_sDocEvent, rBinding.m_sJobName);
290
0
        aCfg.setEnvironment(JobData::E_DOCUMENTEVENT);
291
292
0
        if (!aCfg.hasCorrectContext(aModuleIdentifier))
293
0
            continue;
294
295
        /*Attention!
296
            Jobs implements interfaces and dies by ref count!
297
            And freeing of such uno object is done by uno itself.
298
            So we have to use dynamic memory everytimes.
299
         */
300
0
        css::uno::Reference< css::frame::XModel > xModel(aEvent.Source, css::uno::UNO_QUERY);
301
0
        pJob = new Job(m_xContext, xModel);
302
0
        pJob->setJobData(aCfg);
303
304
0
        pJob->execute(css::uno::Sequence< css::beans::NamedValue >());
305
0
    }
306
0
}
307
308
void SAL_CALL JobExecutor::elementInserted( const css::container::ContainerEvent& aEvent )
309
0
{
310
0
    OUString sValue;
311
0
    if (aEvent.Accessor >>= sValue)
312
0
    {
313
0
        OUString sEvent = ::utl::extractFirstFromConfigurationPath(sValue);
314
0
        if (!sEvent.isEmpty())
315
0
        {
316
0
            std::vector<OUString>::iterator pEvent = std::find(m_lEvents.begin(), m_lEvents.end(), sEvent);
317
0
            if (pEvent == m_lEvents.end())
318
0
                m_lEvents.push_back(sEvent);
319
0
        }
320
0
    }
321
0
}
322
323
void SAL_CALL JobExecutor::elementRemoved ( const css::container::ContainerEvent& aEvent )
324
0
{
325
0
    OUString sValue;
326
0
    if (aEvent.Accessor >>= sValue)
327
0
    {
328
0
        OUString sEvent = ::utl::extractFirstFromConfigurationPath(sValue);
329
0
        if (!sEvent.isEmpty())
330
0
        {
331
0
            std::vector<OUString>::iterator pEvent = std::find(m_lEvents.begin(), m_lEvents.end(), sEvent);
332
0
            if (pEvent != m_lEvents.end())
333
0
                m_lEvents.erase(pEvent);
334
0
        }
335
0
    }
336
0
}
337
338
void SAL_CALL JobExecutor::elementReplaced( const css::container::ContainerEvent& )
339
0
{
340
    // I'm not interested on changed items :-)
341
0
}
342
343
/** @short  the used cfg changes notifier wish to be released in its reference.
344
345
    @descr  We close our internal used configuration instance to
346
            free this reference.
347
348
    @attention  For the special feature "bind global document event broadcaster to job execution"
349
                this job executor instance was registered from outside code as
350
                css.document.XEventListener. So it can be, that this disposing call comes from
351
                the global event broadcaster service. But we don't hold any reference to this service
352
                which can or must be released. Because this broadcaster itself is a one instance service
353
                too, we can ignore this request. On the other side we must release our internal CFG
354
                reference... SOLUTION => check the given event source and react only, if it's our internal
355
                hold configuration object!
356
 */
357
void SAL_CALL JobExecutor::disposing( const css::lang::EventObject& aEvent )
358
0
{
359
    /* SAFE { */
360
0
    std::unique_lock g(m_aMutex);
361
0
    css::uno::Reference< css::uno::XInterface > xCFG(m_aConfig.cfg(), css::uno::UNO_QUERY);
362
0
    if (
363
0
        (xCFG                == aEvent.Source        ) &&
364
0
        (m_aConfig.getMode() != ConfigAccess::E_CLOSED)
365
0
       )
366
0
    {
367
0
        m_aConfig.close();
368
0
    }
369
    /* } SAFE */
370
0
}
371
372
}
373
374
extern "C" SAL_DLLPUBLIC_EXPORT css::uno::XInterface *
375
com_sun_star_comp_framework_JobExecutor_get_implementation(
376
    css::uno::XComponentContext *context,
377
    css::uno::Sequence<css::uno::Any> const &)
378
3
{
379
3
    rtl::Reference<JobExecutor> xJobExec = new JobExecutor(context);
380
    // 2nd phase initialization needed
381
3
    xJobExec->initListeners();
382
3
    return cppu::acquire(xJobExec.get());
383
3
}
384
385
/* vim:set shiftwidth=4 softtabstop=4 expandtab: */