ca8c59c7e6
Change-Id: I3ee54f3967339ab52c04f48a047bc29fc5161680 Reviewed-on: https://gerrit.libreoffice.org/c/core/+/173159 Tested-by: Jenkins Reviewed-by: Noel Grandin <noel.grandin@collabora.co.uk>
385 lines
14 KiB
C++
385 lines
14 KiB
C++
/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
|
|
/*
|
|
* This file is part of the LibreOffice project.
|
|
*
|
|
* This Source Code Form is subject to the terms of the Mozilla Public
|
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
|
*
|
|
* This file incorporates work covered by the following license notice:
|
|
*
|
|
* Licensed to the Apache Software Foundation (ASF) under one or more
|
|
* contributor license agreements. See the NOTICE file distributed
|
|
* with this work for additional information regarding copyright
|
|
* ownership. The ASF licenses this file to you under the Apache
|
|
* License, Version 2.0 (the "License"); you may not use this file
|
|
* except in compliance with the License. You may obtain a copy of
|
|
* the License at http://www.apache.org/licenses/LICENSE-2.0 .
|
|
*/
|
|
|
|
#include <jobs/job.hxx>
|
|
#include <jobs/configaccess.hxx>
|
|
#include <classes/converter.hxx>
|
|
|
|
#include <helper/mischelper.hxx>
|
|
|
|
#include <com/sun/star/container/XNameAccess.hpp>
|
|
#include <com/sun/star/container/XContainer.hpp>
|
|
#include <com/sun/star/frame/ModuleManager.hpp>
|
|
#include <com/sun/star/task/XJobExecutor.hpp>
|
|
#include <com/sun/star/container/XContainerListener.hpp>
|
|
#include <com/sun/star/lang/XServiceInfo.hpp>
|
|
#include <com/sun/star/document/XEventListener.hpp>
|
|
|
|
#include <comphelper/compbase.hxx>
|
|
#include <cppuhelper/supportsservice.hxx>
|
|
#include <comphelper/configuration.hxx>
|
|
#include <unotools/configpaths.hxx>
|
|
#include <rtl/ref.hxx>
|
|
#include <sal/log.hxx>
|
|
#include <vcl/svapp.hxx>
|
|
|
|
using namespace framework;
|
|
|
|
namespace {
|
|
|
|
typedef comphelper::WeakComponentImplHelper<
|
|
css::lang::XServiceInfo
|
|
, css::task::XJobExecutor
|
|
, css::container::XContainerListener // => lang.XEventListener
|
|
, css::document::XEventListener >
|
|
Base;
|
|
|
|
/**
|
|
@short implements a job executor, which can be triggered from any code
|
|
@descr It uses the given trigger event to locate any registered job service
|
|
inside the configuration and execute it. Of course it controls the
|
|
lifetime of such jobs too.
|
|
*/
|
|
class JobExecutor : public Base
|
|
{
|
|
private:
|
|
|
|
/** reference to the uno service manager */
|
|
css::uno::Reference< css::uno::XComponentContext > m_xContext;
|
|
|
|
/** cached list of all registered event names of cfg for call optimization. */
|
|
std::vector<OUString> m_lEvents;
|
|
|
|
/** we listen at the configuration for changes at the event list. */
|
|
ConfigAccess m_aConfig;
|
|
|
|
/** helper to allow us listen to the configuration without a cyclic dependency */
|
|
rtl::Reference<WeakContainerListener> m_xConfigListener;
|
|
|
|
virtual void disposing(std::unique_lock<std::mutex>& rGuard) final override;
|
|
|
|
public:
|
|
|
|
explicit JobExecutor(const css::uno::Reference< css::uno::XComponentContext >& xContext);
|
|
virtual ~JobExecutor() override;
|
|
|
|
virtual OUString SAL_CALL getImplementationName() override
|
|
{
|
|
return u"com.sun.star.comp.framework.JobExecutor"_ustr;
|
|
}
|
|
|
|
virtual sal_Bool SAL_CALL supportsService(OUString const & ServiceName) override
|
|
{
|
|
return cppu::supportsService(this, ServiceName);
|
|
}
|
|
|
|
virtual css::uno::Sequence<OUString> SAL_CALL getSupportedServiceNames() override
|
|
{
|
|
return {u"com.sun.star.task.JobExecutor"_ustr};
|
|
}
|
|
|
|
// task.XJobExecutor
|
|
virtual void SAL_CALL trigger( const OUString& sEvent ) override;
|
|
|
|
/// Initialization function after having acquire()'d.
|
|
void initListeners();
|
|
|
|
// document.XEventListener
|
|
virtual void SAL_CALL notifyEvent( const css::document::EventObject& aEvent ) override;
|
|
|
|
// container.XContainerListener
|
|
virtual void SAL_CALL elementInserted( const css::container::ContainerEvent& aEvent ) override;
|
|
virtual void SAL_CALL elementRemoved ( const css::container::ContainerEvent& aEvent ) override;
|
|
virtual void SAL_CALL elementReplaced( const css::container::ContainerEvent& aEvent ) override;
|
|
|
|
// lang.XEventListener
|
|
virtual void SAL_CALL disposing( const css::lang::EventObject& aEvent ) override;
|
|
};
|
|
|
|
/**
|
|
@short standard ctor
|
|
@descr It initialize this new instance.
|
|
|
|
@param xContext
|
|
reference to the uno service manager
|
|
*/
|
|
JobExecutor::JobExecutor( /*IN*/ const css::uno::Reference< css::uno::XComponentContext >& xContext )
|
|
: m_xContext (xContext )
|
|
, m_aConfig (xContext, u"/org.openoffice.Office.Jobs/Events"_ustr)
|
|
{
|
|
}
|
|
|
|
void JobExecutor::initListeners()
|
|
{
|
|
if (comphelper::IsFuzzing())
|
|
return;
|
|
|
|
// read the list of all currently registered events inside configuration.
|
|
// e.g. "/org.openoffice.Office.Jobs/Events/<event name>"
|
|
// We need it later to check if an incoming event request can be executed successfully
|
|
// or must be rejected. It's an optimization! Of course we must implement updating of this
|
|
// list too ... Be listener at the configuration.
|
|
|
|
m_aConfig.open(ConfigAccess::E_READONLY);
|
|
if (m_aConfig.getMode() != ConfigAccess::E_READONLY)
|
|
return;
|
|
|
|
css::uno::Reference< css::container::XNameAccess > xRegistry(
|
|
m_aConfig.cfg(), css::uno::UNO_QUERY);
|
|
if (xRegistry.is())
|
|
m_lEvents = Converter::convert_seqOUString2OUStringList(
|
|
xRegistry->getElementNames());
|
|
|
|
css::uno::Reference< css::container::XContainer > xNotifier(
|
|
m_aConfig.cfg(), css::uno::UNO_QUERY);
|
|
if (xNotifier.is())
|
|
{
|
|
m_xConfigListener = new WeakContainerListener(this);
|
|
xNotifier->addContainerListener(m_xConfigListener);
|
|
}
|
|
|
|
// don't close cfg here!
|
|
// It will be done inside disposing ...
|
|
}
|
|
|
|
JobExecutor::~JobExecutor()
|
|
{
|
|
std::unique_lock g(m_aMutex);
|
|
disposing(g);
|
|
}
|
|
|
|
void JobExecutor::disposing(std::unique_lock<std::mutex>& /*rGuard*/) {
|
|
css::uno::Reference<css::container::XContainer> notifier;
|
|
css::uno::Reference<css::container::XContainerListener> listener;
|
|
if (m_aConfig.getMode() != ConfigAccess::E_CLOSED) {
|
|
notifier.set(m_aConfig.cfg(), css::uno::UNO_QUERY);
|
|
listener = m_xConfigListener;
|
|
m_aConfig.close();
|
|
}
|
|
m_xConfigListener.clear();
|
|
if (notifier.is()) {
|
|
notifier->removeContainerListener(listener);
|
|
}
|
|
}
|
|
|
|
/**
|
|
@short implementation of XJobExecutor interface
|
|
@descr We use the given event to locate any registered job inside our configuration
|
|
and execute it. Further we control the lifetime of it and suppress
|
|
shutdown of the office till all jobs was finished.
|
|
|
|
@param sEvent
|
|
is used to locate registered jobs
|
|
*/
|
|
void SAL_CALL JobExecutor::trigger( const OUString& sEvent )
|
|
{
|
|
SAL_INFO( "fwk", "JobExecutor::trigger()");
|
|
|
|
/* SAFE */
|
|
{
|
|
std::unique_lock g(m_aMutex);
|
|
|
|
// Optimization!
|
|
// Check if the given event name exist inside configuration and reject wrong requests.
|
|
// This optimization suppress using of the cfg api for getting event and job descriptions ...
|
|
if (std::find(m_lEvents.begin(), m_lEvents.end(), sEvent) == m_lEvents.end())
|
|
return;
|
|
|
|
} /* SAFE */
|
|
|
|
// get list of all enabled jobs
|
|
// The called static helper methods read it from the configuration and
|
|
// filter disabled jobs using it's time stamp values.
|
|
std::vector< OUString > lJobs = JobData::getEnabledJobsForEvent(m_xContext, sEvent);
|
|
|
|
// step over all enabled jobs and execute it
|
|
size_t c = lJobs.size();
|
|
for (size_t j=0; j<c; ++j)
|
|
{
|
|
JobData aCfg(m_xContext);
|
|
aCfg.setEvent(sEvent, lJobs[j]);
|
|
aCfg.setEnvironment(JobData::E_EXECUTION);
|
|
|
|
/*Attention!
|
|
Jobs implements interfaces and dies by ref count!
|
|
And freeing of such uno object is done by uno itself.
|
|
So we have to use dynamic memory everytimes.
|
|
*/
|
|
rtl::Reference<Job> pJob = new Job(m_xContext, css::uno::Reference< css::frame::XFrame >());
|
|
pJob->setJobData(aCfg);
|
|
|
|
pJob->execute(css::uno::Sequence< css::beans::NamedValue >());
|
|
}
|
|
}
|
|
|
|
void SAL_CALL JobExecutor::notifyEvent( const css::document::EventObject& aEvent )
|
|
{
|
|
static constexpr OUString EVENT_ON_DOCUMENT_OPENED(u"onDocumentOpened"_ustr); // Job UI event : OnNew or OnLoad
|
|
static constexpr OUString EVENT_ON_DOCUMENT_ADDED(u"onDocumentAdded"_ustr); // Job API event : OnCreate or OnLoadFinished
|
|
|
|
OUString aModuleIdentifier;
|
|
::std::vector< JobData::TJob2DocEventBinding > lJobs;
|
|
|
|
// Optimization!
|
|
// Check if the given event name exist inside configuration and reject wrong requests.
|
|
// This optimization suppress using of the cfg api for getting event and job descriptions.
|
|
// see using of m_lEvents.find() below ...
|
|
|
|
// retrieve event context from event source
|
|
try
|
|
{
|
|
aModuleIdentifier = css::frame::ModuleManager::create( m_xContext )->identify( aEvent.Source );
|
|
}
|
|
catch( const css::uno::Exception& )
|
|
{}
|
|
|
|
/* SAFE */
|
|
{
|
|
std::unique_lock g(m_aMutex);
|
|
|
|
// Special feature: If the events "OnNew" or "OnLoad" occurs - we generate our own event "onDocumentOpened".
|
|
if (
|
|
(aEvent.EventName == "OnNew") ||
|
|
(aEvent.EventName == "OnLoad")
|
|
)
|
|
{
|
|
if (std::find(m_lEvents.begin(), m_lEvents.end(), EVENT_ON_DOCUMENT_OPENED) != m_lEvents.end())
|
|
JobData::appendEnabledJobsForEvent(m_xContext, EVENT_ON_DOCUMENT_OPENED, lJobs);
|
|
}
|
|
|
|
// Special feature: If the events "OnCreate" or "OnLoadFinished" occurs - we generate our own event "onDocumentAdded".
|
|
if (
|
|
(aEvent.EventName == "OnCreate") ||
|
|
(aEvent.EventName == "OnLoadFinished")
|
|
)
|
|
{
|
|
if (std::find(m_lEvents.begin(), m_lEvents.end(), EVENT_ON_DOCUMENT_ADDED) != m_lEvents.end())
|
|
JobData::appendEnabledJobsForEvent(m_xContext, EVENT_ON_DOCUMENT_ADDED, lJobs);
|
|
}
|
|
|
|
// Add all jobs for "real" notified event too .-)
|
|
if (std::find(m_lEvents.begin(), m_lEvents.end(), aEvent.EventName) != m_lEvents.end())
|
|
JobData::appendEnabledJobsForEvent(m_xContext, aEvent.EventName, lJobs);
|
|
} /* SAFE */
|
|
|
|
// step over all enabled jobs and execute it
|
|
for (auto const& lJob : lJobs)
|
|
{
|
|
rtl::Reference<Job> pJob;
|
|
|
|
const JobData::TJob2DocEventBinding& rBinding = lJob;
|
|
|
|
JobData aCfg(m_xContext);
|
|
aCfg.setEvent(rBinding.m_sDocEvent, rBinding.m_sJobName);
|
|
aCfg.setEnvironment(JobData::E_DOCUMENTEVENT);
|
|
|
|
if (!aCfg.hasCorrectContext(aModuleIdentifier))
|
|
continue;
|
|
|
|
/*Attention!
|
|
Jobs implements interfaces and dies by ref count!
|
|
And freeing of such uno object is done by uno itself.
|
|
So we have to use dynamic memory everytimes.
|
|
*/
|
|
css::uno::Reference< css::frame::XModel > xModel(aEvent.Source, css::uno::UNO_QUERY);
|
|
pJob = new Job(m_xContext, xModel);
|
|
pJob->setJobData(aCfg);
|
|
|
|
pJob->execute(css::uno::Sequence< css::beans::NamedValue >());
|
|
}
|
|
}
|
|
|
|
void SAL_CALL JobExecutor::elementInserted( const css::container::ContainerEvent& aEvent )
|
|
{
|
|
OUString sValue;
|
|
if (aEvent.Accessor >>= sValue)
|
|
{
|
|
OUString sEvent = ::utl::extractFirstFromConfigurationPath(sValue);
|
|
if (!sEvent.isEmpty())
|
|
{
|
|
std::vector<OUString>::iterator pEvent = std::find(m_lEvents.begin(), m_lEvents.end(), sEvent);
|
|
if (pEvent == m_lEvents.end())
|
|
m_lEvents.push_back(sEvent);
|
|
}
|
|
}
|
|
}
|
|
|
|
void SAL_CALL JobExecutor::elementRemoved ( const css::container::ContainerEvent& aEvent )
|
|
{
|
|
OUString sValue;
|
|
if (aEvent.Accessor >>= sValue)
|
|
{
|
|
OUString sEvent = ::utl::extractFirstFromConfigurationPath(sValue);
|
|
if (!sEvent.isEmpty())
|
|
{
|
|
std::vector<OUString>::iterator pEvent = std::find(m_lEvents.begin(), m_lEvents.end(), sEvent);
|
|
if (pEvent != m_lEvents.end())
|
|
m_lEvents.erase(pEvent);
|
|
}
|
|
}
|
|
}
|
|
|
|
void SAL_CALL JobExecutor::elementReplaced( const css::container::ContainerEvent& )
|
|
{
|
|
// I'm not interested on changed items :-)
|
|
}
|
|
|
|
/** @short the used cfg changes notifier wish to be released in its reference.
|
|
|
|
@descr We close our internal used configuration instance to
|
|
free this reference.
|
|
|
|
@attention For the special feature "bind global document event broadcaster to job execution"
|
|
this job executor instance was registered from outside code as
|
|
css.document.XEventListener. So it can be, that this disposing call comes from
|
|
the global event broadcaster service. But we don't hold any reference to this service
|
|
which can or must be released. Because this broadcaster itself is a one instance service
|
|
too, we can ignore this request. On the other side we must release our internal CFG
|
|
reference... SOLUTION => check the given event source and react only, if it's our internal
|
|
hold configuration object!
|
|
*/
|
|
void SAL_CALL JobExecutor::disposing( const css::lang::EventObject& aEvent )
|
|
{
|
|
/* SAFE { */
|
|
std::unique_lock g(m_aMutex);
|
|
css::uno::Reference< css::uno::XInterface > xCFG(m_aConfig.cfg(), css::uno::UNO_QUERY);
|
|
if (
|
|
(xCFG == aEvent.Source ) &&
|
|
(m_aConfig.getMode() != ConfigAccess::E_CLOSED)
|
|
)
|
|
{
|
|
m_aConfig.close();
|
|
}
|
|
/* } SAFE */
|
|
}
|
|
|
|
}
|
|
|
|
extern "C" SAL_DLLPUBLIC_EXPORT css::uno::XInterface *
|
|
com_sun_star_comp_framework_JobExecutor_get_implementation(
|
|
css::uno::XComponentContext *context,
|
|
css::uno::Sequence<css::uno::Any> const &)
|
|
{
|
|
rtl::Reference<JobExecutor> xJobExec = new JobExecutor(context);
|
|
// 2nd phase initialization needed
|
|
xJobExec->initListeners();
|
|
return cppu::acquire(xJobExec.get());
|
|
}
|
|
|
|
/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
|