summaryrefslogtreecommitdiffstats
path: root/framework/source/jobs
diff options
context:
space:
mode:
Diffstat (limited to 'framework/source/jobs')
-rw-r--r--framework/source/jobs/helponstartup.cxx336
-rw-r--r--framework/source/jobs/job.cxx857
-rw-r--r--framework/source/jobs/jobdata.cxx545
-rw-r--r--framework/source/jobs/jobdispatch.cxx474
-rw-r--r--framework/source/jobs/jobexecutor.cxx385
-rw-r--r--framework/source/jobs/jobresult.cxx179
-rw-r--r--framework/source/jobs/joburl.cxx247
-rw-r--r--framework/source/jobs/shelljob.cxx168
8 files changed, 3191 insertions, 0 deletions
diff --git a/framework/source/jobs/helponstartup.cxx b/framework/source/jobs/helponstartup.cxx
new file mode 100644
index 0000000000..2795a3f450
--- /dev/null
+++ b/framework/source/jobs/helponstartup.cxx
@@ -0,0 +1,336 @@
+/* -*- 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 own header
+#include <jobs/helponstartup.hxx>
+#include <services.h>
+#include <targets.h>
+
+#include <officecfg/Office/Common.hxx>
+#include <officecfg/Setup.hxx>
+
+// include others
+#include <comphelper/sequenceashashmap.hxx>
+#include <utility>
+#include <vcl/svapp.hxx>
+#include <vcl/help.hxx>
+
+// include interfaces
+#include <com/sun/star/frame/FrameSearchFlag.hpp>
+#include <com/sun/star/frame/ModuleManager.hpp>
+#include <com/sun/star/frame/XFramesSupplier.hpp>
+#include <com/sun/star/frame/Desktop.hpp>
+#include <cppuhelper/supportsservice.hxx>
+
+namespace framework{
+
+// XInterface, XTypeProvider, XServiceInfo
+
+OUString SAL_CALL HelpOnStartup::getImplementationName()
+{
+ return "com.sun.star.comp.framework.HelpOnStartup";
+}
+
+sal_Bool SAL_CALL HelpOnStartup::supportsService( const OUString& sServiceName )
+{
+ return cppu::supportsService(this, sServiceName);
+}
+
+css::uno::Sequence< OUString > SAL_CALL HelpOnStartup::getSupportedServiceNames()
+{
+ return { SERVICENAME_JOB };
+}
+
+HelpOnStartup::HelpOnStartup(css::uno::Reference< css::uno::XComponentContext > xContext)
+ : m_xContext (std::move(xContext))
+{
+ // create some needed uno services and cache it
+ m_xModuleManager = css::frame::ModuleManager::create( m_xContext );
+
+ m_xDesktop = css::frame::Desktop::create(m_xContext);
+
+ // ask for office locale
+ m_sLocale = officecfg::Setup::L10N::ooLocale::get();
+
+ // detect system
+ m_sSystem = officecfg::Office::Common::Help::System::get();
+
+ // Start listening for disposing events of these services,
+ // so we can react e.g. for an office shutdown
+ css::uno::Reference< css::lang::XComponent > xComponent;
+ xComponent.set(m_xModuleManager, css::uno::UNO_QUERY);
+ if (xComponent.is())
+ xComponent->addEventListener(static_cast< css::lang::XEventListener* >(this));
+ if (m_xDesktop.is())
+ m_xDesktop->addEventListener(static_cast< css::lang::XEventListener* >(this));
+ xComponent.set(m_xConfig, css::uno::UNO_QUERY);
+ if (xComponent.is())
+ xComponent->addEventListener(static_cast< css::lang::XEventListener* >(this));
+}
+
+HelpOnStartup::~HelpOnStartup()
+{
+}
+
+// css.task.XJob
+css::uno::Any SAL_CALL HelpOnStartup::execute(const css::uno::Sequence< css::beans::NamedValue >& lArguments)
+{
+ // Analyze the given arguments; try to locate a model there and
+ // classify it's used application module.
+ OUString sModule = its_getModuleIdFromEnv(lArguments);
+
+ // Attention: we are bound to events for opening any document inside the office.
+ // That includes e.g. the help module itself. But we have to do nothing then!
+ if (sModule.isEmpty())
+ return css::uno::Any();
+
+ // check current state of the help module
+ // a) help isn't open => show default page for the detected module
+ // b) help shows any other default page(!) => show default page for the detected module
+ // c) help shows any other content => do nothing (user travelled to any other content and leaved the set of default pages)
+ OUString sCurrentHelpURL = its_getCurrentHelpURL();
+ bool bCurrentHelpURLIsAnyDefaultURL = its_isHelpUrlADefaultOne(sCurrentHelpURL);
+ bool bShowIt = false;
+
+ // a)
+ if (sCurrentHelpURL.isEmpty())
+ bShowIt = true;
+ // b)
+ else if (bCurrentHelpURLIsAnyDefaultURL)
+ bShowIt = true;
+
+ if (bShowIt)
+ {
+ // retrieve the help URL for the detected application module
+ OUString sModuleDependentHelpURL = its_checkIfHelpEnabledAndGetURL(sModule);
+ if (!sModuleDependentHelpURL.isEmpty())
+ {
+ // Show this help page.
+ // Note: The help window brings itself to front ...
+ Help* pHelp = Application::GetHelp();
+ if (pHelp)
+ pHelp->Start(sModuleDependentHelpURL);
+ }
+ }
+
+ return css::uno::Any();
+}
+
+void SAL_CALL HelpOnStartup::disposing(const css::lang::EventObject& aEvent)
+{
+ std::unique_lock g(m_mutex);
+ if (aEvent.Source == m_xModuleManager)
+ m_xModuleManager.clear();
+ else if (aEvent.Source == m_xDesktop)
+ m_xDesktop.clear();
+ else if (aEvent.Source == m_xConfig)
+ m_xConfig.clear();
+}
+
+OUString HelpOnStartup::its_getModuleIdFromEnv(const css::uno::Sequence< css::beans::NamedValue >& lArguments)
+{
+ ::comphelper::SequenceAsHashMap lArgs (lArguments);
+ ::comphelper::SequenceAsHashMap lEnvironment = lArgs.getUnpackedValueOrDefault("Environment", css::uno::Sequence< css::beans::NamedValue >());
+
+ // check for right environment.
+ // If it's not a DocumentEvent, which triggered this job,
+ // we can't work correctly! => return immediately and do nothing
+ OUString sEnvType = lEnvironment.getUnpackedValueOrDefault("EnvType", OUString());
+ if (sEnvType != "DOCUMENTEVENT")
+ return OUString();
+
+ css::uno::Reference< css::frame::XModel > xDoc = lEnvironment.getUnpackedValueOrDefault("Model", css::uno::Reference< css::frame::XModel >());
+ if (!xDoc.is())
+ return OUString();
+
+ // be sure that we work on top level documents only, which are registered
+ // on the desktop instance. Ignore e.g. life previews, which are top frames too ...
+ // but not registered at this global desktop instance.
+ css::uno::Reference< css::frame::XDesktop > xDesktopCheck;
+ css::uno::Reference< css::frame::XFrame > xFrame;
+ css::uno::Reference< css::frame::XController > xController = xDoc->getCurrentController();
+ if (xController.is())
+ xFrame = xController->getFrame();
+ if (xFrame.is() && xFrame->isTop())
+ xDesktopCheck.set(xFrame->getCreator(), css::uno::UNO_QUERY);
+ if (!xDesktopCheck.is())
+ return OUString();
+
+ // OK - now we are sure this document is a top level document.
+ // Classify it.
+ // SAFE ->
+ std::unique_lock aLock(m_mutex);
+ css::uno::Reference< css::frame::XModuleManager2 > xModuleManager = m_xModuleManager;
+ aLock.unlock();
+ // <- SAFE
+
+ OUString sModuleId;
+ try
+ {
+ sModuleId = xModuleManager->identify(xDoc);
+ }
+ catch(const css::uno::RuntimeException&)
+ { throw; }
+ catch(const css::uno::Exception&)
+ { sModuleId.clear(); }
+
+ return sModuleId;
+}
+
+OUString HelpOnStartup::its_getCurrentHelpURL()
+{
+ // SAFE ->
+ std::unique_lock aLock(m_mutex);
+ css::uno::Reference< css::frame::XDesktop2 > xDesktop = m_xDesktop;
+ aLock.unlock();
+ // <- SAFE
+
+ if (!xDesktop.is())
+ return OUString();
+
+ css::uno::Reference< css::frame::XFrame > xHelp = xDesktop->findFrame(SPECIALTARGET_HELPTASK, css::frame::FrameSearchFlag::CHILDREN);
+ if (!xHelp.is())
+ return OUString();
+
+ OUString sCurrentHelpURL;
+ try
+ {
+ css::uno::Reference< css::frame::XFramesSupplier > xHelpRoot (xHelp , css::uno::UNO_QUERY_THROW);
+ css::uno::Reference< css::container::XIndexAccess > xHelpChildren(xHelpRoot->getFrames(), css::uno::UNO_QUERY_THROW);
+
+ css::uno::Reference< css::frame::XFrame > xHelpChild;
+ css::uno::Reference< css::frame::XController > xHelpView;
+ css::uno::Reference< css::frame::XModel > xHelpContent;
+
+ xHelpChildren->getByIndex(0) >>= xHelpChild;
+ if (xHelpChild.is())
+ xHelpView = xHelpChild->getController();
+ if (xHelpView.is())
+ xHelpContent = xHelpView->getModel();
+ if (xHelpContent.is())
+ sCurrentHelpURL = xHelpContent->getURL();
+ }
+ catch(const css::uno::RuntimeException&)
+ { throw; }
+ catch(const css::uno::Exception&)
+ { sCurrentHelpURL.clear(); }
+
+ return sCurrentHelpURL;
+}
+
+bool HelpOnStartup::its_isHelpUrlADefaultOne(std::u16string_view sHelpURL)
+{
+ if (sHelpURL.empty())
+ return false;
+
+ // SAFE ->
+ std::unique_lock aLock(m_mutex);
+ css::uno::Reference< css::container::XNameAccess > xConfig = m_xConfig;
+ OUString sLocale = m_sLocale;
+ OUString sSystem = m_sSystem;
+ aLock.unlock();
+ // <- SAFE
+
+ if (!xConfig.is())
+ return false;
+
+ // check given help url against all default ones
+ const css::uno::Sequence< OUString > lModules = xConfig->getElementNames();
+ const OUString* pModules = lModules.getConstArray();
+ ::sal_Int32 c = lModules.getLength();
+ ::sal_Int32 i = 0;
+
+ for (i=0; i<c; ++i)
+ {
+ try
+ {
+ css::uno::Reference< css::container::XNameAccess > xModuleConfig;
+ xConfig->getByName(pModules[i]) >>= xModuleConfig;
+ if (!xModuleConfig.is())
+ continue;
+
+ OUString sHelpBaseURL;
+ xModuleConfig->getByName("ooSetupFactoryHelpBaseURL") >>= sHelpBaseURL;
+ OUString sHelpURLForModule = HelpOnStartup::ist_createHelpURL(sHelpBaseURL, sLocale, sSystem);
+ if (sHelpURL == sHelpURLForModule)
+ return true;
+ }
+ catch(const css::uno::RuntimeException&)
+ { throw; }
+ catch(const css::uno::Exception&)
+ {}
+ }
+
+ return false;
+}
+
+OUString HelpOnStartup::its_checkIfHelpEnabledAndGetURL(const OUString& sModule)
+{
+ // SAFE ->
+ std::unique_lock aLock(m_mutex);
+ css::uno::Reference< css::container::XNameAccess > xConfig = m_xConfig;
+ OUString sLocale = m_sLocale;
+ OUString sSystem = m_sSystem;
+ aLock.unlock();
+ // <- SAFE
+
+ OUString sHelpURL;
+
+ try
+ {
+ css::uno::Reference< css::container::XNameAccess > xModuleConfig;
+ if (xConfig.is())
+ xConfig->getByName(sModule) >>= xModuleConfig;
+
+ bool bHelpEnabled = false;
+ if (xModuleConfig.is())
+ xModuleConfig->getByName("ooSetupFactoryHelpOnOpen") >>= bHelpEnabled;
+
+ if (bHelpEnabled)
+ {
+ OUString sHelpBaseURL;
+ xModuleConfig->getByName("ooSetupFactoryHelpBaseURL") >>= sHelpBaseURL;
+ sHelpURL = HelpOnStartup::ist_createHelpURL(sHelpBaseURL, sLocale, sSystem);
+ }
+ }
+ catch(const css::uno::RuntimeException&)
+ { throw; }
+ catch(const css::uno::Exception&)
+ { sHelpURL.clear(); }
+
+ return sHelpURL;
+}
+
+OUString HelpOnStartup::ist_createHelpURL(std::u16string_view sBaseURL,
+ std::u16string_view sLocale ,
+ std::u16string_view sSystem )
+{
+ return OUString::Concat(sBaseURL) + "?Language=" + sLocale + "&System=" + sSystem;
+}
+
+} // namespace framework
+
+extern "C" SAL_DLLPUBLIC_EXPORT css::uno::XInterface*
+framework_HelpOnStartup_get_implementation(
+ css::uno::XComponentContext* context, css::uno::Sequence<css::uno::Any> const& )
+{
+ return cppu::acquire(new framework::HelpOnStartup(context));
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/framework/source/jobs/job.cxx b/framework/source/jobs/job.cxx
new file mode 100644
index 0000000000..711bd47b58
--- /dev/null
+++ b/framework/source/jobs/job.cxx
@@ -0,0 +1,857 @@
+/* -*- 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/jobresult.hxx>
+
+#include <com/sun/star/frame/Desktop.hpp>
+#include <com/sun/star/frame/TerminationVetoException.hpp>
+#include <com/sun/star/task/XJob.hpp>
+#include <com/sun/star/task/XAsyncJob.hpp>
+#include <com/sun/star/util/CloseVetoException.hpp>
+#include <com/sun/star/util/XCloseBroadcaster.hpp>
+#include <com/sun/star/util/XCloseable.hpp>
+#include <com/sun/star/lang/DisposedException.hpp>
+
+#include <comphelper/sequence.hxx>
+#include <sal/log.hxx>
+#include <comphelper/diagnose_ex.hxx>
+#include <utility>
+#include <vcl/svapp.hxx>
+
+namespace framework{
+
+/**
+ @short standard ctor
+ @descr It initialize this new instance. But it set some generic parameters here only.
+ Specialized information (e.g. the alias or service name ofthis job) will be set
+ later using the method setJobData().
+
+ @param xContext
+ reference to the uno service manager
+
+ @param xFrame
+ reference to the frame, in which environment we run
+ (May be null!)
+*/
+Job::Job( /*IN*/ const css::uno::Reference< css::uno::XComponentContext >& xContext ,
+ /*IN*/ css::uno::Reference< css::frame::XFrame > xFrame )
+ : m_aJobCfg (xContext )
+ , m_xContext (xContext )
+ , m_xFrame (std::move(xFrame ))
+ , m_bListenOnDesktop (false )
+ , m_bListenOnFrame (false )
+ , m_bListenOnModel (false )
+ , m_bPendingCloseFrame (false )
+ , m_bPendingCloseModel (false )
+ , m_eRunState (E_NEW )
+{
+}
+
+/**
+ @short standard ctor
+ @descr It initialize this new instance. But it set some generic parameters here only.
+ Specialized information (e.g. the alias or service name ofthis job) will be set
+ later using the method setJobData().
+
+ @param xContext
+ reference to the uno service manager
+
+ @param xModel
+ reference to the model, in which environment we run
+ (May be null!)
+*/
+Job::Job( /*IN*/ const css::uno::Reference< css::uno::XComponentContext >& xContext ,
+ /*IN*/ css::uno::Reference< css::frame::XModel > xModel )
+ : m_aJobCfg (xContext )
+ , m_xContext (xContext )
+ , m_xModel (std::move(xModel ))
+ , m_bListenOnDesktop (false )
+ , m_bListenOnFrame (false )
+ , m_bListenOnModel (false )
+ , m_bPendingCloseFrame (false )
+ , m_bPendingCloseModel (false )
+ , m_eRunState (E_NEW )
+{
+}
+
+/**
+ @short superfluous!
+ @descr Releasing of memory and reference must be done inside die() call.
+ Otherwise it's a bug.
+*/
+Job::~Job()
+{
+}
+
+/**
+ @short set (or delete) a listener for sending dispatch result events
+ @descr Because this object is used in a wrapped mode ... the original listener
+ for such events can't be registered here directly. Because the
+ listener expect to get the original object given as source of the event.
+ That's why we get this source here too, to fake(!) it at sending time!
+
+ @param xListener
+ the original listener for dispatch result events
+
+ @param xSourceFake
+ our user, which got the registration request for this listener
+*/
+void Job::setDispatchResultFake( /*IN*/ const css::uno::Reference< css::frame::XDispatchResultListener >& xListener ,
+ /*IN*/ const css::uno::Reference< css::uno::XInterface >& xSourceFake )
+{
+ SolarMutexGuard g;
+
+ // reject dangerous calls
+ if (m_eRunState != E_NEW)
+ {
+ SAL_INFO("fwk", "Job::setJobData(): job may still running or already finished");
+ return;
+ }
+
+ m_xResultListener = xListener;
+ m_xResultSourceFake = xSourceFake;
+}
+
+void Job::setJobData( const JobData& aData )
+{
+ SolarMutexGuard g;
+
+ // reject dangerous calls
+ if (m_eRunState != E_NEW)
+ {
+ SAL_INFO("fwk", "Job::setJobData(): job may still running or already finished");
+ return;
+ }
+
+ m_aJobCfg = aData;
+}
+
+/**
+ @short runs the job
+ @descr It doesn't matter, if the job is an asynchronous or
+ synchronous one. This method returns only if it was finished
+ or cancelled.
+
+ @param lDynamicArgs
+ optional arguments for job execution
+ In case the represented job is a configured one (which uses static
+ arguments too) all information will be merged!
+*/
+void Job::execute( /*IN*/ const css::uno::Sequence< css::beans::NamedValue >& lDynamicArgs )
+{
+ /* SAFE { */
+ class SolarMutexAntiGuard {
+ SolarMutexResettableGuard & m_rGuard;
+ public:
+ SolarMutexAntiGuard(SolarMutexResettableGuard & rGuard) : m_rGuard(rGuard)
+ {
+ m_rGuard.clear();
+ }
+ ~SolarMutexAntiGuard()
+ {
+ m_rGuard.reset();
+ }
+ };
+ SolarMutexResettableGuard aWriteLock;
+
+ // reject dangerous calls
+ if (m_eRunState != E_NEW)
+ {
+ SAL_INFO("fwk", "Job::execute(): job may still running or already finished");
+ return;
+ }
+
+ // create the environment and mark this job as running ...
+ m_eRunState = E_RUNNING;
+ impl_startListening();
+
+ css::uno::Reference< css::task::XAsyncJob > xAJob;
+ css::uno::Reference< css::task::XJob > xSJob;
+ css::uno::Sequence< css::beans::NamedValue > lJobArgs = impl_generateJobArgs(lDynamicArgs);
+
+ // It's necessary to hold us self alive!
+ // Otherwise we might die by ref count ...
+ css::uno::Reference< css::task::XJobListener > xThis(this);
+
+ try
+ {
+ // create the job
+ // We must check for the supported interface on demand!
+ // But we prefer the synchronous one ...
+ m_xJob = m_xContext->getServiceManager()->createInstanceWithContext(m_aJobCfg.getService(), m_xContext);
+ xSJob.set(m_xJob, css::uno::UNO_QUERY);
+ if (!xSJob.is())
+ xAJob.set(m_xJob, css::uno::UNO_QUERY);
+
+ // execute it asynchronous
+ if (xAJob.is())
+ {
+ m_aAsyncWait.reset();
+ SolarMutexAntiGuard const ag(aWriteLock);
+ /* } SAFE */
+ xAJob->executeAsync(lJobArgs, xThis);
+ // wait for finishing this job - so this method
+ // does the same for synchronous and asynchronous jobs!
+ m_aAsyncWait.wait();
+ /* SAFE { */
+ // Note: Result handling was already done inside the callback!
+ }
+ // execute it synchron
+ else if (xSJob.is())
+ {
+ css::uno::Any aResult;
+ {
+ SolarMutexAntiGuard const ag(aWriteLock);
+ /* } SAFE */
+ aResult = xSJob->execute(lJobArgs);
+ }
+ /* SAFE { */
+ impl_reactForJobResult(aResult);
+ }
+ }
+ #if OSL_DEBUG_LEVEL > 0
+ catch(const css::uno::Exception&)
+ {
+ TOOLS_INFO_EXCEPTION("fwk", "Job::execute(): Got exception during job execution");
+ }
+ #else
+ catch(const css::uno::Exception&)
+ {}
+ #endif
+
+ // deinitialize the environment and mark this job as finished...
+ // but don't overwrite any information about STOPPED or might DISPOSED jobs!
+ impl_stopListening();
+ if (m_eRunState == E_RUNNING)
+ m_eRunState = E_STOPPED_OR_FINISHED;
+
+ // If we got a close request from our frame or model...
+ // but we disagreed with that by throwing a veto exception...
+ // and got the ownership...
+ // we have to close the resource frame or model now -
+ // and to disable ourself!
+ if (m_bPendingCloseFrame)
+ {
+ m_bPendingCloseFrame = false;
+ css::uno::Reference< css::util::XCloseable > xClose(m_xFrame, css::uno::UNO_QUERY);
+ if (xClose.is())
+ {
+ try
+ {
+ xClose->close(true);
+ }
+ catch(const css::util::CloseVetoException&) {}
+ }
+ }
+
+ if (m_bPendingCloseModel)
+ {
+ m_bPendingCloseModel = false;
+ css::uno::Reference< css::util::XCloseable > xClose(m_xModel, css::uno::UNO_QUERY);
+ if (xClose.is())
+ {
+ try
+ {
+ xClose->close(true);
+ }
+ catch(const css::util::CloseVetoException&) {}
+ }
+ }
+
+ aWriteLock.clear();
+ /* SAFE { */
+
+ // release this instance ...
+ die();
+}
+
+/**
+ @short kill this job
+ @descr It doesn't matter if this request is called from inside or
+ from outside. We release our internal structures and stop
+ every activity. After doing so - this instance will not be
+ usable any longer! Of course we try to handle further requests
+ carefully. Maybe someone else holds a reference to us ...
+*/
+void Job::die()
+{
+ SolarMutexGuard g;
+
+ impl_stopListening();
+
+ if (m_eRunState != E_DISPOSED)
+ {
+ try
+ {
+ css::uno::Reference< css::lang::XComponent > xDispose(m_xJob, css::uno::UNO_QUERY);
+ if (xDispose.is())
+ {
+ xDispose->dispose();
+ m_eRunState = E_DISPOSED;
+ }
+ }
+ catch(const css::lang::DisposedException&)
+ {
+ m_eRunState = E_DISPOSED;
+ }
+ }
+
+ m_xJob.clear();
+ m_xFrame.clear();
+ m_xModel.clear();
+ m_xDesktop.clear();
+ m_xResultListener.clear();
+ m_xResultSourceFake.clear();
+ m_bPendingCloseFrame = false;
+ m_bPendingCloseModel = false;
+}
+
+/**
+ @short generates list of arguments for job execute
+ @descr There exist a set of information, which can be needed by a job.
+ a) it's static configuration data (Equals for all jobs. )
+ b) it's specific configuration data (Different for every job.)
+ c) some environment values (e.g. the frame, for which this job was started)
+ d) any other dynamic data (e.g. parameters of a dispatch() request)
+ We collect all this information and generate one list which include all others.
+
+ @param lDynamicArgs
+ list of dynamic arguments (given by a corresponding dispatch() call)
+ Can be empty too.
+
+ @return A list which includes all mentioned sub lists.
+*/
+css::uno::Sequence< css::beans::NamedValue > Job::impl_generateJobArgs( /*IN*/ const css::uno::Sequence< css::beans::NamedValue >& lDynamicArgs )
+{
+ css::uno::Sequence< css::beans::NamedValue > lAllArgs;
+
+ /* SAFE { */
+ SolarMutexClearableGuard aReadLock;
+
+ // the real structure of the returned list depends from the environment of this job!
+ JobData::EMode eMode = m_aJobCfg.getMode();
+
+ // Create list of environment variables. This list must be part of the
+ // returned structure every time... but some of its members are optional!
+ sal_Int32 nLen = 1;
+ if (m_xFrame.is())
+ ++nLen;
+ if (m_xModel.is())
+ ++nLen;
+ if (eMode==JobData::E_EVENT)
+ ++nLen;
+ css::uno::Sequence< css::beans::NamedValue > lEnvArgs(nLen);
+ auto plEnvArgs = lEnvArgs.getArray();
+ plEnvArgs[0].Name = "EnvType";
+ plEnvArgs[0].Value <<= m_aJobCfg.getEnvironmentDescriptor();
+
+ sal_Int32 i = 0;
+ if (m_xFrame.is())
+ {
+ ++i;
+ plEnvArgs[i].Name = "Frame";
+ plEnvArgs[i].Value <<= m_xFrame;
+ }
+ if (m_xModel.is())
+ {
+ ++i;
+ plEnvArgs[i].Name = "Model";
+ plEnvArgs[i].Value <<= m_xModel;
+ }
+ if (eMode==JobData::E_EVENT)
+ {
+ ++i;
+ plEnvArgs[i].Name = "EventName";
+ plEnvArgs[i].Value <<= m_aJobCfg.getEvent();
+ }
+
+ // get the configuration data from the job data container ... if possible
+ // Means: if this job has any configuration data. Note: only really
+ // filled lists will be set to the return structure at the end of this method.
+ css::uno::Sequence< css::beans::NamedValue > lConfigArgs;
+ std::vector< css::beans::NamedValue > lJobConfigArgs;
+ if (eMode==JobData::E_ALIAS || eMode==JobData::E_EVENT)
+ {
+ lConfigArgs = m_aJobCfg.getConfig();
+ lJobConfigArgs = m_aJobCfg.getJobConfig();
+ }
+
+ aReadLock.clear();
+ /* } SAFE */
+
+ // Add all valid (not empty) lists to the return list
+ if (lConfigArgs.hasElements())
+ {
+ sal_Int32 nLength = lAllArgs.getLength();
+ lAllArgs.realloc(nLength+1);
+ auto plAllArgs = lAllArgs.getArray();
+ plAllArgs[nLength].Name = "Config";
+ plAllArgs[nLength].Value <<= lConfigArgs;
+ }
+ if (!lJobConfigArgs.empty())
+ {
+ sal_Int32 nLength = lAllArgs.getLength();
+ lAllArgs.realloc(nLength+1);
+ auto plAllArgs = lAllArgs.getArray();
+ plAllArgs[nLength].Name = "JobConfig";
+ plAllArgs[nLength].Value <<= comphelper::containerToSequence(lJobConfigArgs);
+ }
+ if (lEnvArgs.hasElements())
+ {
+ sal_Int32 nLength = lAllArgs.getLength();
+ lAllArgs.realloc(nLength+1);
+ auto plAllArgs = lAllArgs.getArray();
+ plAllArgs[nLength].Name = "Environment";
+ plAllArgs[nLength].Value <<= lEnvArgs;
+ }
+ if (lDynamicArgs.hasElements())
+ {
+ sal_Int32 nLength = lAllArgs.getLength();
+ lAllArgs.realloc(nLength+1);
+ auto plAllArgs = lAllArgs.getArray();
+ plAllArgs[nLength].Name = "DynamicData";
+ plAllArgs[nLength].Value <<= lDynamicArgs;
+ }
+
+ return lAllArgs;
+}
+
+/**
+ @short analyze the given job result and change the job configuration
+ @descr Note: Some results can be handled only, if this job has a valid configuration!
+ For "not configured jobs" (means pure services) they can be ignored.
+ But these cases are handled by our JobData member. We can call it every time.
+ It does the right things automatically. E.g. if the job has no configuration ...
+ it does nothing during setJobConfig()!
+
+ @param aResult
+ the job result for analyzing
+*/
+void Job::impl_reactForJobResult( /*IN*/ const css::uno::Any& aResult )
+{
+ SolarMutexGuard g;
+
+ // analyze the result set ...
+ JobResult aAnalyzedResult(aResult);
+
+ // some of the following operations will be supported for different environments
+ // or different type of jobs only.
+ JobData::EEnvironment eEnvironment = m_aJobCfg.getEnvironment();
+
+ // write back the job specific configuration data ...
+ // If the environment allow it and if this job has a configuration!
+ if (
+ (m_aJobCfg.hasConfig() ) &&
+ (aAnalyzedResult.existPart(JobResult::E_ARGUMENTS))
+ )
+ {
+ m_aJobCfg.setJobConfig(aAnalyzedResult.getArguments());
+ }
+
+ // disable a job for further executions.
+ // Note: this option is available inside the environment EXECUTOR only
+ if (
+// (eEnvironment == JobData::E_EXECUTION ) &&
+ (m_aJobCfg.hasConfig() ) &&
+ (aAnalyzedResult.existPart(JobResult::E_DEACTIVATE))
+ )
+ {
+ m_aJobCfg.disableJob();
+ }
+
+ // notify any interested listener with the may given result state.
+ // Note: this option is available inside the environment DISPATCH only
+ if (
+ (eEnvironment == JobData::E_DISPATCH ) &&
+ (m_xResultListener.is() ) &&
+ (aAnalyzedResult.existPart(JobResult::E_DISPATCHRESULT))
+ )
+ {
+ // Attention: Because the listener expect that the original object send this event ...
+ // and we nor the job are the right ones ...
+ // our user has set itself before. So we can fake this source address!
+ css::frame::DispatchResultEvent aEvent = aAnalyzedResult.getDispatchResult();
+ aEvent.Source = m_xResultSourceFake;
+ m_xResultListener->dispatchFinished(aEvent);
+ }
+}
+
+/**
+ @short starts listening for office shutdown and closing of our
+ given target frame (if it's a valid reference)
+ @descr We will register ourself as terminate listener
+ at the global desktop instance. That will hold us
+ alive and additional we get the information, if the
+ office wish to shutdown. If then an internal job
+ is running we will have the chance to suppress that
+ by throwing a veto exception. If our internal wrapped
+ job finished his work, we can release this listener
+ connection.
+
+ Further we are listener for closing of the (possible valid)
+ given frame. We must be sure, that this resource won't be gone
+ if our internal job is still running.
+*/
+void Job::impl_startListening()
+{
+ SolarMutexGuard g;
+
+ // listening for office shutdown
+ if (!m_xDesktop.is() && !m_bListenOnDesktop)
+ {
+ try
+ {
+ m_xDesktop = css::frame::Desktop::create( m_xContext );
+ css::uno::Reference< css::frame::XTerminateListener > xThis(this);
+ m_xDesktop->addTerminateListener(xThis);
+ m_bListenOnDesktop = true;
+ }
+ catch(const css::uno::Exception&)
+ {
+ m_xDesktop.clear();
+ }
+ }
+
+ // listening for frame closing
+ if (m_xFrame.is() && !m_bListenOnFrame)
+ {
+ try
+ {
+ css::uno::Reference< css::util::XCloseBroadcaster > xCloseable(m_xFrame , css::uno::UNO_QUERY);
+ css::uno::Reference< css::util::XCloseListener > xThis(this);
+ if (xCloseable.is())
+ {
+ xCloseable->addCloseListener(xThis);
+ m_bListenOnFrame = true;
+ }
+ }
+ catch(const css::uno::Exception&)
+ {
+ m_bListenOnFrame = false;
+ }
+ }
+
+ // listening for model closing
+ if (!m_xModel.is() || m_bListenOnModel)
+ return;
+
+ try
+ {
+ css::uno::Reference< css::util::XCloseBroadcaster > xCloseable(m_xModel , css::uno::UNO_QUERY);
+ css::uno::Reference< css::util::XCloseListener > xThis(this);
+ if (xCloseable.is())
+ {
+ xCloseable->addCloseListener(xThis);
+ m_bListenOnModel = true;
+ }
+ }
+ catch(const css::uno::Exception&)
+ {
+ m_bListenOnModel = false;
+ }
+}
+
+/**
+ @short release listener connection for office shutdown
+ @descr see description of impl_startListening()
+*/
+void Job::impl_stopListening()
+{
+ SolarMutexGuard g;
+
+ // stop listening for office shutdown
+ if (m_xDesktop.is() && m_bListenOnDesktop)
+ {
+ try
+ {
+ css::uno::Reference< css::frame::XTerminateListener > xThis(this);
+ m_xDesktop->removeTerminateListener(xThis);
+ m_xDesktop.clear();
+ m_bListenOnDesktop = false;
+ }
+ catch(const css::uno::Exception&)
+ {
+ }
+ }
+
+ // stop listening for frame closing
+ if (m_xFrame.is() && m_bListenOnFrame)
+ {
+ try
+ {
+ css::uno::Reference< css::util::XCloseBroadcaster > xCloseable(m_xFrame , css::uno::UNO_QUERY);
+ css::uno::Reference< css::util::XCloseListener > xThis(this);
+ if (xCloseable.is())
+ {
+ xCloseable->removeCloseListener(xThis);
+ m_bListenOnFrame = false;
+ }
+ }
+ catch(const css::uno::Exception&)
+ {
+ }
+ }
+
+ // stop listening for model closing
+ if (!(m_xModel.is() && m_bListenOnModel))
+ return;
+
+ try
+ {
+ css::uno::Reference< css::util::XCloseBroadcaster > xCloseable(m_xModel , css::uno::UNO_QUERY);
+ css::uno::Reference< css::util::XCloseListener > xThis(this);
+ if (xCloseable.is())
+ {
+ xCloseable->removeCloseListener(xThis);
+ m_bListenOnModel = false;
+ }
+ }
+ catch(const css::uno::Exception&)
+ {
+ }
+}
+
+/**
+ @short callback from any asynchronous executed job
+
+ @descr Our execute() method waits for this callback.
+ We have to react for the possible results here,
+ to kill the running job and disable the blocked condition
+ so execute() can be finished too.
+
+ @param xJob
+ the job, which was running and inform us now
+
+ @param aResult
+ its results
+*/
+void SAL_CALL Job::jobFinished( /*IN*/ const css::uno::Reference< css::task::XAsyncJob >& xJob ,
+ /*IN*/ const css::uno::Any& aResult )
+{
+ SolarMutexGuard g;
+
+ // It's necessary to check this.
+ // May this job was cancelled by any other reason
+ // some milliseconds before. :-)
+ if (m_xJob.is() && m_xJob==xJob)
+ {
+ // react for his results
+ // (means enable/disable it for further requests
+ // or save arguments or notify listener ...)
+ impl_reactForJobResult(aResult);
+
+ // Let the job die!
+ m_xJob.clear();
+ }
+
+ // And let the start method "execute()" finishing it's job.
+ // But do it every time. So any outside blocking code can finish
+ // his work too.
+ m_aAsyncWait.set();
+}
+
+/**
+ @short prevent internal wrapped job against office termination
+ @descr This event is broadcasted by the desktop instance and ask for an office termination.
+ If the internal wrapped job is still in progress, we disagree with that by throwing the
+ right veto exception. If not - we agree. But then we must be aware, that another event
+ notifyTermination() can follow. Then we have no chance to do the same. Then we have to
+ accept that and stop our work instandly.
+
+ @param aEvent
+ describes the broadcaster and must be the desktop instance
+
+ @throw TerminateVetoException
+ if our internal wrapped job is still running.
+ */
+void SAL_CALL Job::queryTermination( /*IN*/ const css::lang::EventObject& )
+{
+ SolarMutexGuard g;
+
+ // Otherwise try to close() it
+ css::uno::Reference< css::util::XCloseable > xClose(m_xJob, css::uno::UNO_QUERY);
+ if (xClose.is())
+ {
+ try
+ {
+ xClose->close(false);
+ m_eRunState = E_STOPPED_OR_FINISHED;
+ }
+ catch(const css::util::CloseVetoException&) {}
+ }
+
+ if (m_eRunState != E_STOPPED_OR_FINISHED)
+ {
+ css::uno::Reference< css::uno::XInterface > xThis(static_cast< ::cppu::OWeakObject* >(this), css::uno::UNO_QUERY);
+ throw css::frame::TerminationVetoException("job still in progress", xThis);
+ }
+}
+
+/**
+ @short inform us about office termination
+ @descr Instead of the method queryTermination(), here is no chance to disagree with that.
+ We have to accept it and cancel all current processes inside.
+ It can occur only, if job was not already started if queryTermination() was called here.
+ Then we had not thrown a veto exception. But now we must agree with this situation and break
+ all our internal processes. It's not a good idea to mark this instance as non startable any longer
+ inside queryTermination() if no job was running too. Because that would disable this job and may
+ the office does not really shutdown, because another listener has thrown the suitable exception.
+
+ @param aEvent
+ describes the broadcaster and must be the desktop instance
+ */
+void SAL_CALL Job::notifyTermination( /*IN*/ const css::lang::EventObject& )
+{
+ die();
+ // Do nothing else here. Our internal resources was released ...
+}
+
+/**
+ @short prevent internal wrapped job against frame closing
+ @descr This event is broadcasted by the frame instance and ask for closing.
+ If the internal wrapped job is still in progress, we disagree with that by throwing the
+ right veto exception. If not - we agree. But then we must be aware, that another event
+ notifyClosing() can follow. Then we have no chance to do the same. Then we have to
+ accept that and stop our work instandly.
+
+ @param aEvent
+ describes the broadcaster and must be the frame instance
+
+ @param bGetsOwnership
+ If it's set to <sal_True> and we throw the right veto exception, we have to close this frame later
+ if our internal processes will be finished. If it's set to <FALSE/> we can ignore it.
+
+ @throw CloseVetoException
+ if our internal wrapped job is still running.
+ */
+void SAL_CALL Job::queryClosing( const css::lang::EventObject& aEvent ,
+ sal_Bool bGetsOwnership )
+{
+ SolarMutexGuard g;
+
+ // do nothing, if no internal job is still running ...
+ // The frame or model can be closed then successfully.
+ if (m_eRunState != E_RUNNING)
+ return;
+
+ // try close() first at the job.
+ // The job can agree or disagree with this request.
+ css::uno::Reference< css::util::XCloseable > xClose(m_xJob, css::uno::UNO_QUERY);
+ if (xClose.is())
+ {
+ xClose->close(bGetsOwnership);
+ // Here we can say: "this job was stopped successfully". Because
+ // no veto exception was thrown!
+ m_eRunState = E_STOPPED_OR_FINISHED;
+ return;
+ }
+
+ // try dispose() then
+ // Here the job has no chance for a veto.
+ // But we must be aware of an "already disposed exception"...
+ try
+ {
+ css::uno::Reference< css::lang::XComponent > xDispose(m_xJob, css::uno::UNO_QUERY);
+ if (xDispose.is())
+ {
+ xDispose->dispose();
+ m_eRunState = E_DISPOSED;
+ }
+ }
+ catch(const css::lang::DisposedException&)
+ {
+ // the job was already disposed by any other mechanism !?
+ // But it's not interesting for us. For us this job is stopped now.
+ m_eRunState = E_DISPOSED;
+ }
+
+ if (m_eRunState != E_DISPOSED)
+ {
+ // analyze event source - to find out, which resource called queryClosing() at this
+ // job wrapper. We must bind a "pending close" request to this resource.
+ // Closing of the corresponding resource will be done if our internal job finish it's work.
+ m_bPendingCloseFrame = (m_xFrame.is() && aEvent.Source == m_xFrame);
+ m_bPendingCloseModel = (m_xModel.is() && aEvent.Source == m_xModel);
+
+ // throw suitable veto exception - because the internal job could not be cancelled.
+ css::uno::Reference< css::uno::XInterface > xThis(static_cast< ::cppu::OWeakObject* >(this), css::uno::UNO_QUERY);
+ throw css::util::CloseVetoException("job still in progress", xThis);
+ }
+
+ // No veto ...
+ // But don't call die() here or free our internal member.
+ // This must be done inside notifyClosing() only. Otherwise the
+ // might stopped job has no chance to return its results or
+ // call us back. We must give him the chance to finish it's work successfully.
+}
+
+/**
+ @short inform us about frame closing
+ @descr Instead of the method queryClosing(), here is no chance to disagree with that.
+ We have to accept it and cancel all current processes inside.
+
+ @param aEvent
+ describes the broadcaster and must be the frame or model instance we know
+ */
+void SAL_CALL Job::notifyClosing( const css::lang::EventObject& )
+{
+ die();
+ // Do nothing else here. Our internal resources was released ...
+}
+
+/**
+ @short shouldn't be called normally
+ @descr But it doesn't matter, who called it. We have to kill our internal
+ running processes hardly.
+
+ @param aEvent
+ describe the broadcaster
+*/
+void SAL_CALL Job::disposing( const css::lang::EventObject& aEvent )
+{
+ /* SAFE { */
+ {
+ SolarMutexGuard aWriteLock;
+
+ if (m_xDesktop.is() && aEvent.Source == m_xDesktop)
+ {
+ m_xDesktop.clear();
+ m_bListenOnDesktop = false;
+ }
+ else if (m_xFrame.is() && aEvent.Source == m_xFrame)
+ {
+ m_xFrame.clear();
+ m_bListenOnFrame = false;
+ }
+ else if (m_xModel.is() && aEvent.Source == m_xModel)
+ {
+ m_xModel.clear();
+ m_bListenOnModel = false;
+ }
+ }
+ /* } SAFE */
+
+ die();
+ // Do nothing else here. Our internal resources was released ...
+}
+
+} // namespace framework
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/framework/source/jobs/jobdata.cxx b/framework/source/jobs/jobdata.cxx
new file mode 100644
index 0000000000..0ca06fcaca
--- /dev/null
+++ b/framework/source/jobs/jobdata.cxx
@@ -0,0 +1,545 @@
+/* -*- 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 <sal/config.h>
+
+#include <string_view>
+
+#include <jobs/configaccess.hxx>
+#include <jobs/jobdata.hxx>
+#include <classes/converter.hxx>
+
+#include <com/sun/star/beans/XPropertySet.hpp>
+#include <com/sun/star/beans/XMultiHierarchicalPropertySet.hpp>
+#include <com/sun/star/container/XNameAccess.hpp>
+#include <com/sun/star/container/XHierarchicalNameAccess.hpp>
+
+#include <tools/wldcrd.hxx>
+#include <unotools/configpaths.hxx>
+#include <utility>
+#include <vcl/svapp.hxx>
+
+namespace framework{
+
+/**
+ @short standard ctor
+ @descr It initialize this new instance.
+ But for real working it's necessary to call setAlias() or setService() later.
+ Because we need the job data ...
+
+ @param rxContext
+ reference to the uno service manager
+*/
+JobData::JobData( css::uno::Reference< css::uno::XComponentContext > xContext )
+ : m_xContext (std::move(xContext ))
+{
+ // share code for member initialization with defaults!
+ impl_reset();
+}
+
+/**
+ @short copy ctor
+ @descr Sometimes such job data container must be moved from one using place
+ to another one. Then a copy ctor and copy operator must be available.
+
+ @param rCopy
+ the original instance, from which we must copy all data
+*/
+JobData::JobData( const JobData& rCopy )
+{
+ // use the copy operator to share the same code
+ *this = rCopy;
+}
+
+/**
+ @short operator for copying JobData instances
+ @descr Sometimes such job data container must be moved from one using place
+ to another one. Then a copy ctor and copy operator must be available.
+
+ @param rCopy
+ the original instance, from which we must copy all data
+*/
+JobData& JobData::operator=( const JobData& rCopy )
+{
+ // Please don't copy the uno service manager reference.
+ // That can change the uno context, which isn't a good idea!
+ m_eMode = rCopy.m_eMode;
+ m_eEnvironment = rCopy.m_eEnvironment;
+ m_sAlias = rCopy.m_sAlias;
+ m_sService = rCopy.m_sService;
+ m_sContext = rCopy.m_sContext;
+ m_sEvent = rCopy.m_sEvent;
+ m_lArguments = rCopy.m_lArguments;
+ return *this;
+}
+
+/**
+ @short let this instance die
+ @descr There is no chance any longer to work. We have to
+ release all used resources and free used memory.
+*/
+JobData::~JobData()
+{
+ impl_reset();
+}
+
+/**
+ @short initialize this instance as a job with configuration
+ @descr They given alias can be used to address some configuration data.
+ We read it and fill our internal structures. Of course old information
+ will be lost doing so.
+
+ @param sAlias
+ the alias name of this job, used to locate job properties inside cfg
+*/
+void JobData::setAlias( const OUString& sAlias )
+{
+ // delete all old information! Otherwise we mix it with the new one ...
+ impl_reset();
+
+ // take over the new information
+ m_sAlias = sAlias;
+ m_eMode = E_ALIAS;
+
+ // try to open the configuration set of this job directly and get a property access to it
+ // We open it readonly here
+ ConfigAccess aConfig(
+ m_xContext,
+ ("/org.openoffice.Office.Jobs/Jobs/"
+ + utl::wrapConfigurationElementName(m_sAlias)));
+ aConfig.open(ConfigAccess::E_READONLY);
+ if (aConfig.getMode()==ConfigAccess::E_CLOSED)
+ {
+ impl_reset();
+ return;
+ }
+
+ css::uno::Reference< css::beans::XPropertySet > xJobProperties(aConfig.cfg(), css::uno::UNO_QUERY);
+ if (xJobProperties.is())
+ {
+ css::uno::Any aValue;
+
+ // read uno implementation name
+ aValue = xJobProperties->getPropertyValue("Service");
+ aValue >>= m_sService;
+
+ // read module context list
+ aValue = xJobProperties->getPropertyValue("Context");
+ aValue >>= m_sContext;
+
+ // read whole argument list
+ aValue = xJobProperties->getPropertyValue("Arguments");
+ css::uno::Reference< css::container::XNameAccess > xArgumentList;
+ if (
+ (aValue >>= xArgumentList) &&
+ (xArgumentList.is() )
+ )
+ {
+ css::uno::Sequence< OUString > lArgumentNames = xArgumentList->getElementNames();
+ sal_Int32 nCount = lArgumentNames.getLength();
+ m_lArguments.resize(nCount);
+ for (sal_Int32 i=0; i<nCount; ++i)
+ {
+ m_lArguments[i].Name = lArgumentNames[i];
+ m_lArguments[i].Value = xArgumentList->getByName(m_lArguments[i].Name);
+ }
+ }
+ }
+
+ aConfig.close();
+}
+
+/**
+ @short initialize this instance as a job without configuration
+ @descr This job has no configuration data. We have to forget all old information
+ and set only some of them new, so this instance can work.
+
+ @param sService
+ the uno service name of this "non configured" job
+*/
+void JobData::setService( const OUString& sService )
+{
+ // delete all old information! Otherwise we mix it with the new one ...
+ impl_reset();
+ // take over the new information
+ m_sService = sService;
+ m_eMode = E_SERVICE;
+}
+
+/**
+ @short initialize this instance with new job values.
+ @descr It reads automatically all properties of the specified
+ job (using it's alias name) and "register it" for the
+ given event. This registration will not be validated against
+ the underlying configuration! (That must be done from outside.
+ Because the caller must have the configuration already open to
+ get the values for sEvent and sAlias! And doing so it can perform
+ only, if the time stamp values are read outside too.
+ Further it makes no sense to initialize and start a disabled job.
+ So this initialization method will be called for enabled jobs only.)
+
+ @param sEvent
+ the triggered event, for which this job should be started
+
+ @param sAlias
+ mark the required job inside event registration list
+*/
+void JobData::setEvent( const OUString& sEvent ,
+ const OUString& sAlias )
+{
+ // share code to read all job properties!
+ setAlias(sAlias);
+
+ // take over the new information - which differ against set one of method setAlias()!
+ m_sEvent = sEvent;
+ m_eMode = E_EVENT;
+}
+
+/**
+ @short set the new job specific arguments
+ @descr If a job finish his work, it can give us a new list of arguments (which
+ will not interpreted by us). We write it back to the configuration only
+ (if this job has its own configuration!).
+ So a job can have persistent data without implementing anything
+ or define own config areas for that.
+
+ @param lArguments
+ list of arguments, which should be set for this job
+ */
+void JobData::setJobConfig( std::vector< css::beans::NamedValue >&& lArguments )
+{
+ // update member
+ m_lArguments = std::move(lArguments);
+
+ // update the configuration ... if possible!
+ if (m_eMode!=E_ALIAS)
+ return;
+
+ // It doesn't matter if this config object was already opened before.
+ // It doesn nothing here then ... or it change the mode automatically, if
+ // it was opened using another one before.
+ ConfigAccess aConfig(
+ m_xContext,
+ ("/org.openoffice.Office.Jobs/Jobs/"
+ + utl::wrapConfigurationElementName(m_sAlias)));
+ aConfig.open(ConfigAccess::E_READWRITE);
+ if (aConfig.getMode()==ConfigAccess::E_CLOSED)
+ return;
+
+ css::uno::Reference< css::beans::XMultiHierarchicalPropertySet > xArgumentList(aConfig.cfg(), css::uno::UNO_QUERY);
+ if (xArgumentList.is())
+ {
+ sal_Int32 nCount = m_lArguments.size();
+ css::uno::Sequence< OUString > lNames (nCount);
+ auto lNamesRange = asNonConstRange(lNames);
+ css::uno::Sequence< css::uno::Any > lValues(nCount);
+ auto lValuesRange = asNonConstRange(lValues);
+
+ for (sal_Int32 i=0; i<nCount; ++i)
+ {
+ lNamesRange [i] = m_lArguments[i].Name;
+ lValuesRange[i] = m_lArguments[i].Value;
+ }
+
+ xArgumentList->setHierarchicalPropertyValues(lNames, lValues);
+ }
+ aConfig.close();
+}
+
+/**
+ @short set a new environment descriptor for this job
+ @descr It must(!) be done every time this container is initialized
+ with new job data e.g.: setAlias()/setEvent()/setService() ...
+ Otherwise the environment will be unknown!
+ */
+void JobData::setEnvironment( EEnvironment eEnvironment )
+{
+ m_eEnvironment = eEnvironment;
+}
+
+/**
+ @short these functions provides access to our internal members
+ @descr These member represent any information about the job
+ and can be used from outside to e.g. start a job.
+ */
+JobData::EMode JobData::getMode() const
+{
+ return m_eMode;
+}
+
+JobData::EEnvironment JobData::getEnvironment() const
+{
+ return m_eEnvironment;
+}
+
+OUString JobData::getEnvironmentDescriptor() const
+{
+ OUString sDescriptor;
+ switch(m_eEnvironment)
+ {
+ case E_EXECUTION :
+ sDescriptor = "EXECUTOR";
+ break;
+
+ case E_DISPATCH :
+ sDescriptor = "DISPATCH";
+ break;
+
+ case E_DOCUMENTEVENT :
+ sDescriptor = "DOCUMENTEVENT";
+ break;
+ default:
+ break;
+ }
+ return sDescriptor;
+}
+
+OUString JobData::getService() const
+{
+ return m_sService;
+}
+
+OUString JobData::getEvent() const
+{
+ return m_sEvent;
+}
+
+std::vector< css::beans::NamedValue > JobData::getJobConfig() const
+{
+ return m_lArguments;
+}
+
+css::uno::Sequence< css::beans::NamedValue > JobData::getConfig() const
+{
+ css::uno::Sequence< css::beans::NamedValue > lConfig;
+ if (m_eMode==E_ALIAS)
+ {
+ lConfig = { { "Alias", css::uno::Any(m_sAlias) },
+ { "Service", css::uno::Any(m_sService) },
+ { "Context", css::uno::Any(m_sContext) } };
+ }
+ return lConfig;
+}
+
+/**
+ @short return information, if this job is part of the global configuration package
+ org.openoffice.Office.Jobs
+ @descr Because jobs can be executed by the dispatch framework using a uno service name
+ directly - an executed job must not have any configuration really. Such jobs
+ must provide the right interfaces only! But after finishing jobs can return
+ some information (e.g. for updating her configuration ...). We must know
+ if such request is valid or not then.
+
+ @return sal_True if the represented job is part of the underlying configuration package.
+ */
+bool JobData::hasConfig() const
+{
+ return (m_eMode==E_ALIAS || m_eMode==E_EVENT);
+}
+
+/**
+ @short mark a job as non startable for further requests
+ @descr We don't remove the configuration entry! We set a timestamp value only.
+ And there exist two of them: one for an administrator... and one for the
+ current user. We change it for the user layer only. So this JobDispatch can't be
+ started any more... till the administrator change his timestamp.
+ That can be useful for post setup scenarios, which must run one time only.
+
+ Note: This method don't do anything, if this represented job doesn't have a configuration!
+ */
+void JobData::disableJob()
+{
+ // No configuration - not used from EXECUTOR and not triggered from an event => no chance!
+ if (m_eMode!=E_EVENT)
+ return;
+
+ // update the configuration
+ // It doesn't matter if this config object was already opened before.
+ // It doesn nothing here then ... or it change the mode automatically, if
+ // it was opened using another one before.
+ ConfigAccess aConfig(
+ m_xContext,
+ ("/org.openoffice.Office.Jobs/Events/"
+ + utl::wrapConfigurationElementName(m_sEvent) + "/JobList/"
+ + utl::wrapConfigurationElementName(m_sAlias)));
+ aConfig.open(ConfigAccess::E_READWRITE);
+ if (aConfig.getMode()==ConfigAccess::E_CLOSED)
+ return;
+
+ css::uno::Reference< css::beans::XPropertySet > xPropSet(aConfig.cfg(), css::uno::UNO_QUERY);
+ if (xPropSet.is())
+ {
+ // Convert and write the user timestamp to the configuration.
+ css::uno::Any aValue;
+ aValue <<= Converter::convert_DateTime2ISO8601(DateTime( DateTime::SYSTEM));
+ xPropSet->setPropertyValue("UserTime", aValue);
+ }
+
+ aConfig.close();
+}
+
+static bool isEnabled( std::u16string_view sAdminTime ,
+ std::u16string_view sUserTime )
+{
+ /*Attention!
+ To prevent interpreting of TriGraphs inside next const string value,
+ we have to encode all '?' signs. Otherwise e.g. "??-" will be translated
+ to "~" ...
+ */
+ WildCard aISOPattern(u"\?\?\?\?-\?\?-\?\?*");
+
+ bool bValidAdmin = aISOPattern.Matches(sAdminTime);
+ bool bValidUser = aISOPattern.Matches(sUserTime );
+
+ // We check for "isEnabled()" here only.
+ // Note further: ISO8601 formatted strings can be compared as strings directly!
+ // FIXME: this is not true! "T1215" is the same time as "T12:15" or "T121500"
+ return (
+ (!bValidAdmin && !bValidUser ) ||
+ ( bValidAdmin && bValidUser && sAdminTime>=sUserTime)
+ );
+}
+
+void JobData::appendEnabledJobsForEvent( const css::uno::Reference< css::uno::XComponentContext >& rxContext,
+ const OUString& sEvent ,
+ ::std::vector< JobData::TJob2DocEventBinding >& lJobs )
+{
+ std::vector< OUString > lAdditionalJobs = JobData::getEnabledJobsForEvent(rxContext, sEvent);
+ sal_Int32 c = lAdditionalJobs.size();
+ sal_Int32 i = 0;
+
+ for (i=0; i<c; ++i)
+ {
+ JobData::TJob2DocEventBinding aBinding(lAdditionalJobs[i], sEvent);
+ lJobs.push_back(aBinding);
+ }
+}
+
+bool JobData::hasCorrectContext(std::u16string_view rModuleIdent) const
+{
+ sal_Int32 nContextLen = m_sContext.getLength();
+ sal_Int32 nModuleIdLen = rModuleIdent.size();
+
+ if ( nContextLen == 0 )
+ return true;
+
+ if ( nModuleIdLen > 0 )
+ {
+ sal_Int32 nIndex = m_sContext.indexOf( rModuleIdent );
+ if ( nIndex >= 0 && ( nIndex+nModuleIdLen <= nContextLen ))
+ {
+ std::u16string_view sContextModule = m_sContext.subView( nIndex, nModuleIdLen );
+ return sContextModule == rModuleIdent;
+ }
+ }
+
+ return false;
+}
+
+std::vector< OUString > JobData::getEnabledJobsForEvent( const css::uno::Reference< css::uno::XComponentContext >& rxContext,
+ std::u16string_view sEvent )
+{
+ // create a config access to "/org.openoffice.Office.Jobs/Events"
+ ConfigAccess aConfig(rxContext, "/org.openoffice.Office.Jobs/Events");
+ aConfig.open(ConfigAccess::E_READONLY);
+ if (aConfig.getMode()==ConfigAccess::E_CLOSED)
+ return std::vector< OUString >();
+
+ css::uno::Reference< css::container::XHierarchicalNameAccess > xEventRegistry(aConfig.cfg(), css::uno::UNO_QUERY);
+ if (!xEventRegistry.is())
+ return std::vector< OUString >();
+
+ // check if the given event exist inside list of registered ones
+ OUString sPath(OUString::Concat(sEvent) + "/JobList");
+ if (!xEventRegistry->hasByHierarchicalName(sPath))
+ return std::vector< OUString >();
+
+ // step to the job list, which is a child of the event node inside cfg
+ // e.g. "/org.openoffice.Office.Jobs/Events/<event name>/JobList"
+ css::uno::Any aJobList = xEventRegistry->getByHierarchicalName(sPath);
+ css::uno::Reference< css::container::XNameAccess > xJobList;
+ if (!(aJobList >>= xJobList) || !xJobList.is())
+ return std::vector< OUString >();
+
+ // get all alias names of jobs, which are part of this job list
+ // But Some of them can be disabled by its timestamp values.
+ // We create an additional job name list with the same size, then the original list...
+ // step over all job entries... check her timestamps... and put only job names to the
+ // destination list, which represent an enabled job.
+ const css::uno::Sequence< OUString > lAllJobs = xJobList->getElementNames();
+ sal_Int32 c = lAllJobs.getLength();
+
+ std::vector< OUString > lEnabledJobs(c);
+ sal_Int32 d = 0;
+
+ for (OUString const & jobName : lAllJobs)
+ {
+ css::uno::Reference< css::beans::XPropertySet > xJob;
+ if (
+ !(xJobList->getByName(jobName) >>= xJob) ||
+ !(xJob.is() )
+ )
+ {
+ continue;
+ }
+
+ OUString sAdminTime;
+ xJob->getPropertyValue("AdminTime") >>= sAdminTime;
+
+ OUString sUserTime;
+ xJob->getPropertyValue("UserTime") >>= sUserTime;
+
+ if (!isEnabled(sAdminTime, sUserTime))
+ continue;
+
+ lEnabledJobs[d] = jobName;
+ ++d;
+ }
+ lEnabledJobs.resize(d);
+
+ aConfig.close();
+
+ return lEnabledJobs;
+}
+
+/**
+ @short reset all internal structures
+ @descr If someone recycles this instance, he can switch from one
+ using mode to another one. But then we have to reset all currently
+ used information. Otherwise we mix it and they can make trouble.
+
+ But note: that does not set defaults for internal used members, which
+ does not relate to any job property! e.g. the reference to the global
+ uno service manager. Such information is used for internal processes only
+ and are necessary for our work.
+ */
+void JobData::impl_reset()
+{
+ m_eMode = E_UNKNOWN_MODE;
+ m_eEnvironment = E_UNKNOWN_ENVIRONMENT;
+ m_sAlias.clear();
+ m_sService.clear();
+ m_sContext.clear();
+ m_sEvent.clear();
+ m_lArguments.clear();
+}
+
+} // namespace framework
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/framework/source/jobs/jobdispatch.cxx b/framework/source/jobs/jobdispatch.cxx
new file mode 100644
index 0000000000..2352919dea
--- /dev/null
+++ b/framework/source/jobs/jobdispatch.cxx
@@ -0,0 +1,474 @@
+/* -*- 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/configaccess.hxx>
+#include <jobs/joburl.hxx>
+#include <jobs/job.hxx>
+#include <classes/converter.hxx>
+
+#include <com/sun/star/frame/DispatchResultEvent.hpp>
+#include <com/sun/star/frame/DispatchResultState.hpp>
+#include <com/sun/star/frame/ModuleManager.hpp>
+#include <com/sun/star/frame/XNotifyingDispatch.hpp>
+#include <com/sun/star/frame/XDispatch.hpp>
+#include <com/sun/star/frame/XStatusListener.hpp>
+#include <com/sun/star/frame/XDispatchResultListener.hpp>
+#include <com/sun/star/frame/XDispatchProvider.hpp>
+#include <com/sun/star/lang/XInitialization.hpp>
+#include <com/sun/star/lang/XServiceInfo.hpp>
+
+#include <cppuhelper/supportsservice.hxx>
+#include <cppuhelper/implbase.hxx>
+#include <rtl/ref.hxx>
+#include <utility>
+#include <vcl/svapp.hxx>
+
+using namespace framework;
+
+namespace {
+
+/**
+ @short implements a dispatch object for jobs
+ @descr Such dispatch object will be used by the generic dispatch mechanism if
+ a URL "vnd.sun.star.job:alias=<name>" occurs.
+ Then an instance of this class will be created and used.
+ This new instance will be called within his method
+ dispatch() or dispatchWithNotification() for executing the
+ real job. We do it, control the life cycle of this internal
+ wrapped job and inform any interested listener if it finish.
+ */
+class JobDispatch : public ::cppu::WeakImplHelper<
+ css::lang::XServiceInfo
+ , css::lang::XInitialization
+ , css::frame::XDispatchProvider
+ , css::frame::XNotifyingDispatch > // => XDispatch
+{
+private:
+
+ /** reference to the uno service manager */
+ css::uno::Reference< css::uno::XComponentContext > m_xContext;
+
+ /** reference to the frame, inside which this dispatch is used */
+ css::uno::Reference< css::frame::XFrame > m_xFrame;
+
+ /** name of module (writer, impress etc.) the frame is for */
+ OUString m_sModuleIdentifier;
+
+// native interface methods
+
+public:
+
+ explicit JobDispatch(css::uno::Reference< css::uno::XComponentContext > xContext);
+ virtual ~JobDispatch() override;
+
+ void impl_dispatchEvent ( const OUString& sEvent ,
+ const css::uno::Sequence< css::beans::PropertyValue >& lArgs ,
+ const css::uno::Reference< css::frame::XDispatchResultListener >& xListener );
+ void impl_dispatchService( const OUString& sService ,
+ const css::uno::Sequence< css::beans::PropertyValue >& lArgs ,
+ const css::uno::Reference< css::frame::XDispatchResultListener >& xListener );
+ void impl_dispatchAlias ( const OUString& sAlias ,
+ const css::uno::Sequence< css::beans::PropertyValue >& lArgs ,
+ const css::uno::Reference< css::frame::XDispatchResultListener >& xListener );
+
+public:
+ virtual OUString SAL_CALL getImplementationName() override
+ {
+ return "com.sun.star.comp.framework.jobs.JobDispatch";
+ }
+
+ 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 {"com.sun.star.frame.ProtocolHandler"};
+ }
+
+ // Xinitialization
+ virtual void SAL_CALL initialize( const css::uno::Sequence< css::uno::Any >& lArguments ) override;
+
+ // XDispatchProvider
+ virtual css::uno::Reference< css::frame::XDispatch > SAL_CALL queryDispatch ( const css::util::URL& aURL ,
+ const OUString& sTargetFrameName ,
+ sal_Int32 nSearchFlags ) override;
+ virtual css::uno::Sequence< css::uno::Reference< css::frame::XDispatch > > SAL_CALL queryDispatches( const css::uno::Sequence< css::frame::DispatchDescriptor >& lDescriptor ) override;
+
+ // XNotifyingDispatch
+ virtual void SAL_CALL dispatchWithNotification( const css::util::URL& aURL ,
+ const css::uno::Sequence< css::beans::PropertyValue >& lArgs ,
+ const css::uno::Reference< css::frame::XDispatchResultListener >& xListener ) override;
+
+ // XDispatch
+ virtual void SAL_CALL dispatch ( const css::util::URL& aURL ,
+ const css::uno::Sequence< css::beans::PropertyValue >& lArgs ) override;
+ virtual void SAL_CALL addStatusListener ( const css::uno::Reference< css::frame::XStatusListener >& xListener ,
+ const css::util::URL& aURL ) override;
+ virtual void SAL_CALL removeStatusListener( const css::uno::Reference< css::frame::XStatusListener >& xListener ,
+ const css::util::URL& aURL ) override;
+};
+
+/**
+ @short standard ctor
+ @descr It initialize this new instance.
+
+ @param xContext
+ reference to the uno service manager
+*/
+JobDispatch::JobDispatch( /*IN*/ css::uno::Reference< css::uno::XComponentContext > xContext )
+ : m_xContext (std::move(xContext ))
+{
+}
+
+/**
+ @short let this instance die
+ @descr We have to release all used resources and free used memory.
+*/
+JobDispatch::~JobDispatch()
+{
+ // release all used resources
+ m_xContext.clear();
+ m_xFrame.clear();
+}
+
+/**
+ @short implementation of XInitialization
+ @descr A protocol handler can provide this functionality, if it wish to get additional information
+ about the context it runs. In this case the frame reference would be given by the outside code.
+
+ @param lArguments
+ the list of initialization arguments
+ First parameter should be the frame reference we need.
+*/
+void SAL_CALL JobDispatch::initialize( const css::uno::Sequence< css::uno::Any >& lArguments )
+{
+ SolarMutexGuard g;
+
+ for (int a=0; a<lArguments.getLength(); ++a)
+ {
+ if (a==0)
+ {
+ lArguments[a] >>= m_xFrame;
+
+ css::uno::Reference< css::frame::XModuleManager2 > xModuleManager =
+ css::frame::ModuleManager::create(m_xContext);
+ try
+ {
+ m_sModuleIdentifier = xModuleManager->identify( m_xFrame );
+ }
+ catch( const css::uno::Exception& )
+ {}
+ }
+ }
+}
+
+/**
+ @short implementation of XDispatchProvider::queryDispatches()
+ @descr Every protocol handler will be asked for his agreement, if a URL was queried
+ for which this handler is registered. It's the chance for this handler to validate
+ the given URL and return a dispatch object (may be itself) or not.
+
+ @param aURL
+ the queried URL, which should be checked
+
+ @param sTargetFrameName
+ describes the target frame, in which context this handler will be used
+ Is mostly set to "", "_self", "_blank", "_default" or a non special one
+ using SELF/CREATE as search flags.
+
+ @param nSearchFlags
+ Can be SELF or CREATE only and are set only if sTargetFrameName isn't a special target
+*/
+css::uno::Reference< css::frame::XDispatch > SAL_CALL JobDispatch::queryDispatch( /*IN*/ const css::util::URL& aURL ,
+ /*IN*/ const OUString& /*sTargetFrameName*/ ,
+ /*IN*/ sal_Int32 /*nSearchFlags*/ )
+{
+ css::uno::Reference< css::frame::XDispatch > xDispatch;
+
+ JobURL aAnalyzedURL(aURL.Complete);
+ if (aAnalyzedURL.isValid())
+ xDispatch = this;
+
+ return xDispatch;
+}
+
+/**
+ @short implementation of XDispatchProvider::queryDispatches()
+ @descr It's an optimized access for remote, so you can ask for
+ multiple dispatch objects at the same time.
+
+ @param lDescriptor
+ a list of queryDispatch() parameter
+
+ @return A list of corresponding dispatch objects.
+ NULL references are not skipped. Every result
+ match to one given descriptor item.
+*/
+css::uno::Sequence< css::uno::Reference< css::frame::XDispatch > > SAL_CALL JobDispatch::queryDispatches( const css::uno::Sequence< css::frame::DispatchDescriptor >& lDescriptor )
+{
+ // don't pack resulting list!
+ sal_Int32 nCount = lDescriptor.getLength();
+ css::uno::Sequence< css::uno::Reference< css::frame::XDispatch > > lDispatches(nCount);
+ auto lDispatchesRange = asNonConstRange(lDispatches);
+ for (sal_Int32 i=0; i<nCount; ++i)
+ lDispatchesRange[i] = queryDispatch( lDescriptor[i].FeatureURL ,
+ lDescriptor[i].FrameName ,
+ lDescriptor[i].SearchFlags );
+ return lDispatches;
+}
+
+/**
+ @short implementation of XNotifyingDispatch::dispatchWithNotification()
+ @descr It creates the job service implementation and call execute on it.
+ Further it starts the life time control of it. (important for async job)
+ For synchronous job we react for the returned result directly ... for asynchronous
+ ones we do it later inside our callback method. But we use the same impl method
+ doing that to share the code. (see impl_finishJob())
+
+ If a job is already running, (it can only occur for asynchronous jobs)
+ don't start the same job a second time. Queue in the given dispatch parameter
+ and return immediately. If the current running job call us back, we will start this
+ new dispatch request.
+ If no job is running - queue the parameter too! But then start the new job immediately.
+ We have to queue it every time - because it hold us alive by ref count!
+
+ @param aURL
+ describe the job(s), which should be started
+
+ @param lArgs
+ optional arguments for this request
+
+ @param xListener
+ an interested listener for possible results of this operation
+*/
+void SAL_CALL JobDispatch::dispatchWithNotification( /*IN*/ const css::util::URL& aURL ,
+ /*IN*/ const css::uno::Sequence< css::beans::PropertyValue >& lArgs ,
+ /*IN*/ const css::uno::Reference< css::frame::XDispatchResultListener >& xListener )
+{
+ JobURL aAnalyzedURL(aURL.Complete);
+ if (aAnalyzedURL.isValid())
+ {
+ OUString sRequest;
+ if (aAnalyzedURL.getEvent(sRequest))
+ impl_dispatchEvent(sRequest, lArgs, xListener);
+ else
+ if (aAnalyzedURL.getService(sRequest))
+ impl_dispatchService(sRequest, lArgs, xListener);
+ else
+ if (aAnalyzedURL.getAlias(sRequest))
+ impl_dispatchAlias(sRequest, lArgs, xListener);
+ }
+}
+
+/**
+ @short dispatch an event
+ @descr We search all registered jobs for this event and execute it.
+ After doing so, we inform the given listener about the results.
+ (There will be one notify for every executed job!)
+
+ @param sEvent
+ the event, for which jobs can be registered
+
+ @param lArgs
+ optional arguments for this request
+ Currently not used!
+
+ @param xListener
+ an interested listener for possible results of this operation
+*/
+void JobDispatch::impl_dispatchEvent( /*IN*/ const OUString& sEvent ,
+ /*IN*/ const css::uno::Sequence< css::beans::PropertyValue >& lArgs ,
+ /*IN*/ const css::uno::Reference< css::frame::XDispatchResultListener >& xListener )
+{
+ // 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);
+
+ css::uno::Reference< css::frame::XDispatchResultListener > xThis( static_cast< ::cppu::OWeakObject* >(this), css::uno::UNO_QUERY );
+
+ // no jobs... no execution
+ // But a may given listener will know something...
+ // I think this operation was finished successfully.
+ // It's not really an error, if no registered jobs could be located.
+ // Step over all found jobs and execute it
+ int nExecutedJobs=0;
+ for (const OUString & lJob : lJobs)
+ {
+ JobData aCfg(m_xContext);
+ aCfg.setEvent(sEvent, lJob);
+ aCfg.setEnvironment(JobData::E_DISPATCH);
+ const bool bIsEnabled=aCfg.hasCorrectContext(m_sModuleIdentifier);
+
+ rtl::Reference<Job> pJob = new Job(m_xContext, m_xFrame);
+ pJob->setJobData(aCfg);
+
+ if (!bIsEnabled)
+ continue;
+
+ // Special mode for listener.
+ // We don't notify it directly here. We delegate that
+ // to the job implementation. But we must set ourself there too.
+ // Because this job must fake the source address of the event.
+ // Otherwise the listener may ignore it.
+ if (xListener.is())
+ pJob->setDispatchResultFake(xListener, xThis);
+ pJob->execute(Converter::convert_seqPropVal2seqNamedVal(lArgs));
+ ++nExecutedJobs;
+ }
+
+ if (nExecutedJobs<1 && xListener.is())
+ {
+ css::frame::DispatchResultEvent aEvent;
+ aEvent.Source = xThis;
+ aEvent.State = css::frame::DispatchResultState::SUCCESS;
+ xListener->dispatchFinished(aEvent);
+ }
+}
+
+/**
+ @short dispatch a service
+ @descr We use the given name only to create and if possible to initialize
+ it as a uno service. It can be useful for creating (caching?)
+ of e.g. one instance services.
+
+ @param sService
+ the uno implementation or service name of the job, which should be instantiated
+
+ @param lArgs
+ optional arguments for this request
+ Currently not used!
+
+ @param xListener
+ an interested listener for possible results of this operation
+*/
+void JobDispatch::impl_dispatchService( /*IN*/ const OUString& sService ,
+ /*IN*/ const css::uno::Sequence< css::beans::PropertyValue >& lArgs ,
+ /*IN*/ const css::uno::Reference< css::frame::XDispatchResultListener >& xListener )
+{
+ JobData aCfg(m_xContext);
+ aCfg.setService(sService);
+ aCfg.setEnvironment(JobData::E_DISPATCH);
+
+ /*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, m_xFrame);
+ pJob->setJobData(aCfg);
+
+ css::uno::Reference< css::frame::XDispatchResultListener > xThis( static_cast< ::cppu::OWeakObject* >(this), css::uno::UNO_QUERY );
+
+ // Special mode for listener.
+ // We don't notify it directly here. We delegate that
+ // to the job implementation. But we must set ourself there too.
+ // Because this job must fake the source address of the event.
+ // Otherwise the listener may ignore it.
+ if (xListener.is())
+ pJob->setDispatchResultFake(xListener, xThis);
+ pJob->execute(Converter::convert_seqPropVal2seqNamedVal(lArgs));
+}
+
+/**
+ @short dispatch an alias
+ @descr We use this alias to locate a job inside the configuration
+ and execute it. Further we inform the given listener about the results.
+
+ @param sAlias
+ the alias name of the configured job
+
+ @param lArgs
+ optional arguments for this request
+ Currently not used!
+
+ @param xListener
+ an interested listener for possible results of this operation
+*/
+void JobDispatch::impl_dispatchAlias( /*IN*/ const OUString& sAlias ,
+ /*IN*/ const css::uno::Sequence< css::beans::PropertyValue >& lArgs ,
+ /*IN*/ const css::uno::Reference< css::frame::XDispatchResultListener >& xListener )
+{
+ JobData aCfg(m_xContext);
+ aCfg.setAlias(sAlias);
+ aCfg.setEnvironment(JobData::E_DISPATCH);
+
+ rtl::Reference<Job> pJob = new Job(m_xContext, m_xFrame);
+ pJob->setJobData(aCfg);
+
+ css::uno::Reference< css::frame::XDispatchResultListener > xThis( static_cast< ::cppu::OWeakObject* >(this), css::uno::UNO_QUERY );
+
+ // Special mode for listener.
+ // We don't notify it directly here. We delegate that
+ // to the job implementation. But we must set ourself there too.
+ // Because this job must fake the source address of the event.
+ // Otherwise the listener may ignore it.
+ if (xListener.is())
+ pJob->setDispatchResultFake(xListener, xThis);
+ pJob->execute(Converter::convert_seqPropVal2seqNamedVal(lArgs));
+}
+
+/**
+ @short implementation of XDispatch::dispatch()
+ @descr Because the methods dispatch() and dispatchWithNotification() are different in her parameters
+ only, we can forward this request to dispatchWithNotification() by using an empty listener!
+
+ @param aURL
+ describe the job(s), which should be started
+
+ @param lArgs
+ optional arguments for this request
+
+ @see dispatchWithNotification()
+*/
+void SAL_CALL JobDispatch::dispatch( /*IN*/ const css::util::URL& aURL ,
+ /*IN*/ const css::uno::Sequence< css::beans::PropertyValue >& lArgs )
+{
+ dispatchWithNotification(aURL, lArgs, css::uno::Reference< css::frame::XDispatchResultListener >());
+}
+
+/**
+ @short not supported
+*/
+void SAL_CALL JobDispatch::addStatusListener( /*IN*/ const css::uno::Reference< css::frame::XStatusListener >&,
+ /*IN*/ const css::util::URL& )
+{
+}
+
+/**
+ @short not supported
+*/
+void SAL_CALL JobDispatch::removeStatusListener( /*IN*/ const css::uno::Reference< css::frame::XStatusListener >&,
+ /*IN*/ const css::util::URL& )
+{
+}
+
+}
+
+extern "C" SAL_DLLPUBLIC_EXPORT css::uno::XInterface *
+com_sun_star_comp_framework_jobs_JobDispatch_get_implementation(
+ css::uno::XComponentContext *context,
+ css::uno::Sequence<css::uno::Any> const &)
+{
+ return cppu::acquire(new JobDispatch(context));
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/framework/source/jobs/jobexecutor.cxx b/framework/source/jobs/jobexecutor.cxx
new file mode 100644
index 0000000000..6180653858
--- /dev/null
+++ b/framework/source/jobs/jobexecutor.cxx
@@ -0,0 +1,385 @@
+/* -*- 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 <unotools/configmgr.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 */
+ css::uno::Reference<css::container::XContainerListener> 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 "com.sun.star.comp.framework.JobExecutor";
+ }
+
+ 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 {"com.sun.star.task.JobExecutor"};
+ }
+
+ // 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, "/org.openoffice.Office.Jobs/Events")
+{
+}
+
+void JobExecutor::initListeners()
+{
+ if (utl::ConfigManager::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: */
diff --git a/framework/source/jobs/jobresult.cxx b/framework/source/jobs/jobresult.cxx
new file mode 100644
index 0000000000..183543606b
--- /dev/null
+++ b/framework/source/jobs/jobresult.cxx
@@ -0,0 +1,179 @@
+/* -*- 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/jobresult.hxx>
+#include <jobs/jobconst.hxx>
+
+#include <vcl/svapp.hxx>
+#include <comphelper/sequenceashashmap.hxx>
+#include <comphelper/sequence.hxx>
+
+namespace framework
+{
+/**
+ @short special ctor
+ @descr It initialize this new instance with a pure job execution result
+ and analyze it. Doing so, we update our other members.
+
+ <p>
+ It's a list of named values, packed inside this any.
+ Following protocol is used:
+ <p>
+ <ul>
+ <li>
+ "SaveArguments" [sequence< css.beans.NamedValue >]
+ <br>
+ The returned list of (for this generic implementation unknown!)
+ properties, will be written directly to the configuration and replace
+ any old values there. There will no check for changes and we don't
+ support any merge feature here. They are written only. The job has
+ to modify this list.
+ </li>
+ <li>
+ "SendDispatchResult" [css.frame.DispatchResultEvent]
+ <br>
+ The given event is send to all current registered listener.
+ But it's not guaranteed. In case no listener are available or
+ this job isn't part of the dispatch environment (because it was used
+ by the css..task.XJobExecutor->trigger() implementation) this option
+ will be ignored.
+ </li>
+ <li>
+ "Deactivate" [boolean]
+ <br>
+ The job wish to be disabled. But note: There is no way, to enable it later
+ again by using this implementation. It can be done by using the configuration
+ only. (Means to register this job again.)
+ If a job knows, that there exist some status or result listener, it must use
+ the options "SendDispatchStatus" and "SendDispatchResult" (see before) too, to
+ inform it about the deactivation of this service.
+ </li>
+ </ul>
+
+ @param aResult
+ the job result
+*/
+JobResult::JobResult(/*IN*/ const css::uno::Any& aResult)
+{
+ // reset the flag mask!
+ // It will reset the accessible state of this object.
+ // That can be useful if something will fail here ...
+ m_eParts = E_NOPART;
+
+ // analyze the result and update our other members
+ ::comphelper::SequenceAsHashMap aProtocol(aResult);
+ if (aProtocol.empty())
+ return;
+
+ ::comphelper::SequenceAsHashMap::const_iterator pIt
+ = aProtocol.find(JobConst::ANSWER_DEACTIVATE_JOB);
+ if (pIt != aProtocol.end())
+ {
+ /**
+ an executed job can force his deactivation
+ But we provide this information here only.
+ Doing so is part of any user of us.
+ */
+ bool bDeactivate(false);
+ pIt->second >>= bDeactivate;
+ if (bDeactivate)
+ m_eParts |= E_DEACTIVATE;
+ }
+
+ pIt = aProtocol.find(JobConst::ANSWER_SAVE_ARGUMENTS);
+ if (pIt != aProtocol.end())
+ {
+ css::uno::Sequence<css::beans::NamedValue> aTmp;
+ pIt->second >>= aTmp;
+ comphelper::sequenceToContainer(m_lArguments, aTmp);
+ if (m_lArguments.empty())
+ m_eParts |= E_ARGUMENTS;
+ }
+
+ pIt = aProtocol.find(JobConst::ANSWER_SEND_DISPATCHRESULT);
+ if (pIt != aProtocol.end())
+ {
+ if (pIt->second >>= m_aDispatchResult)
+ m_eParts |= E_DISPATCHRESULT;
+ }
+}
+
+/**
+ @short copy dtor
+*/
+JobResult::JobResult(const JobResult& rCopy)
+{
+ m_eParts = rCopy.m_eParts;
+ m_lArguments = rCopy.m_lArguments;
+ m_aDispatchResult = rCopy.m_aDispatchResult;
+}
+
+/**
+ @short standard dtor
+ @descr Free all internally used resources at the end of living.
+*/
+JobResult::~JobResult()
+{
+ // Nothing really to do here.
+}
+
+/**
+ @short =operator
+ @descr Must be implemented to overwrite this instance with another one.
+
+ @param rCopy
+ reference to the other instance, which should be used for copying.
+*/
+JobResult& JobResult::operator=(const JobResult& rCopy)
+{
+ m_eParts = rCopy.m_eParts;
+ m_lArguments = rCopy.m_lArguments;
+ m_aDispatchResult = rCopy.m_aDispatchResult;
+ return *this;
+}
+
+/**
+ @short checks for existing parts of the analyzed result
+ @descr The internal flag mask was set after analyzing of the pure result.
+ An user of us can check here, if the required part was really part
+ of this result. Otherwise it would use invalid information ...
+ by using our other members!
+
+ @param eParts
+ a flag mask too, which will be compared with our internally set one.
+
+ @return We return true only, if any set flag of the given mask match.
+*/
+bool JobResult::existPart(sal_uInt32 eParts) const { return ((m_eParts & eParts) == eParts); }
+
+/**
+ @short provides access to our internal members
+ @descr The return value will be valid only in case a call of
+ existPart(E_...) before returned true!
+
+ @return It returns the state of the internal member
+ without any checks!
+*/
+std::vector<css::beans::NamedValue> JobResult::getArguments() const { return m_lArguments; }
+
+css::frame::DispatchResultEvent JobResult::getDispatchResult() const { return m_aDispatchResult; }
+
+} // namespace framework
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/framework/source/jobs/joburl.cxx b/framework/source/jobs/joburl.cxx
new file mode 100644
index 0000000000..5533014edf
--- /dev/null
+++ b/framework/source/jobs/joburl.cxx
@@ -0,0 +1,247 @@
+/* -*- 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 <sal/config.h>
+
+#include <cstring>
+
+#include <jobs/joburl.hxx>
+
+#include <vcl/svapp.hxx>
+#include <o3tl/string_view.hxx>
+
+namespace framework{
+
+/**
+ @short special ctor
+ @descr It initialize this new instance with a (hopefully) valid job URL.
+ This URL will be parsed. After that we set our members right,
+ so other interface methods of this class can be used to get
+ all items of this URL. Of course it will be possible to know,
+ if this URL was valid too.
+
+ @param sURL
+ the job URL for parsing
+*/
+JobURL::JobURL( /*IN*/ const OUString& sURL )
+{
+ m_eRequest = E_UNKNOWN;
+
+ // syntax: vnd.sun.star.job:{[event=<name>],[alias=<name>],[service=<name>]}
+
+ // check for "vnd.sun.star.job:"
+ if (!sURL.startsWithIgnoreAsciiCase("vnd.sun.star.job:"))
+ return;
+
+ sal_Int32 t = std::strlen("vnd.sun.star.job:");
+ do
+ {
+ // separate all token of "{[event=<name>],[alias=<name>],[service=<name>]}"
+ OUString sToken = sURL.getToken(0, JOBURL_PART_SEPARATOR, t);
+ OUString sPartValue;
+ OUString sPartArguments;
+
+ // check for "event="
+ if (
+ (JobURL::implst_split(sToken,JOBURL_EVENT_STR,JOBURL_EVENT_LEN,sPartValue,sPartArguments)) &&
+ (!sPartValue.isEmpty())
+ )
+ {
+ // set the part value
+ m_sEvent = sPartValue;
+ m_eRequest |= E_EVENT;
+ }
+ else
+ // check for "alias="
+ if (
+ (JobURL::implst_split(sToken,JOBURL_ALIAS_STR,JOBURL_ALIAS_LEN,sPartValue,sPartArguments)) &&
+ (!sPartValue.isEmpty())
+ )
+ {
+ // set the part value
+ m_sAlias = sPartValue;
+ m_eRequest |= E_ALIAS;
+ }
+ else
+ // check for "service="
+ if (
+ (JobURL::implst_split(sToken,JOBURL_SERVICE_STR,JOBURL_SERVICE_LEN,sPartValue,sPartArguments)) &&
+ (!sPartValue.isEmpty())
+ )
+ {
+ // set the part value
+ m_sService = sPartValue;
+ m_eRequest |= E_SERVICE;
+ }
+ }
+ while(t!=-1);
+}
+
+/**
+ @short knows, if this job URL object hold a valid URL inside
+
+ @return <TRUE/> if it represent a valid job URL.
+*/
+bool JobURL::isValid() const
+{
+ return (m_eRequest!=E_UNKNOWN);
+}
+
+/**
+ @short get the event item of this job URL
+ @descr Because the three possible parts of such URL (event, alias, service)
+ can't be combined, this method can(!) return a valid value - but it's
+ not a must. That's why the return value must be used too, to detect a missing
+ event value.
+
+ @param sEvent
+ returns the possible existing event value
+ e.g. "vnd.sun.star.job:event=myEvent" returns "myEvent"
+
+ @return <TRUE/> if an event part of the job URL exist and the out parameter
+ sEvent was filled.
+
+ @attention The out parameter will be reset every time. Don't use it if method returns <FALSE/>!
+*/
+bool JobURL::getEvent( /*OUT*/ OUString& sEvent ) const
+{
+ sEvent.clear();
+ bool bSet = ((m_eRequest & E_EVENT) == E_EVENT);
+ if (bSet)
+ sEvent = m_sEvent;
+
+ return bSet;
+}
+
+/**
+ @short get the alias item of this job URL
+ @descr Because the three possible parts of such URL (event, alias, service)
+ can't be combined, this method can(!) return a valid value - but it's
+ not a must. that's why the return value must be used too, to detect a missing
+ alias value.
+
+ @param sAlias
+ returns the possible existing alias value
+ e.g. "vnd.sun.star.job:alias=myAlias" returns "myAlias"
+
+ @return <TRUE/> if an alias part of the job URL exist and the out parameter
+ sAlias was filled.
+
+ @attention The out parameter will be reset every time. Don't use it if method returns <FALSE/>!
+*/
+bool JobURL::getAlias( /*OUT*/ OUString& sAlias ) const
+{
+ sAlias.clear();
+ bool bSet = ((m_eRequest & E_ALIAS) == E_ALIAS);
+ if (bSet)
+ sAlias = m_sAlias;
+
+ return bSet;
+}
+
+/**
+ @short get the service item of this job URL
+ @descr Because the three possible parts of such URL (event, service, service)
+ can't be combined, this method can(!) return a valid value - but it's
+ not a must. That's why the return value must be used too, to detect a missing
+ service value.
+
+ @param sAlias
+ returns the possible existing service value
+ e.g. "vnd.sun.star.job:service=com.sun.star.Service" returns "com.sun.star.Service"
+
+ @return <TRUE/> if a service part of the job URL exist and the out parameter
+ sService was filled.
+
+ @attention The out parameter will be reset every time. Don't use it if method returns <FALSE/>!
+*/
+bool JobURL::getService( /*OUT*/ OUString& sService ) const
+{
+ sService.clear();
+ bool bSet = ((m_eRequest & E_SERVICE) == E_SERVICE);
+ if (bSet)
+ sService = m_sService;
+
+ return bSet;
+}
+
+/**
+ @short searches for a special identifier in the given string and split it
+ @descr If the given identifier could be found at the beginning of the given string,
+ this method split it into different parts and return it.
+ Following schema is used: <partidentifier>=<partvalue>[?<partarguments>]
+
+ @param sPart
+ the string, which should be analyzed
+
+ @param pPartIdentifier
+ the part identifier value, which must be found at the beginning of the
+ parameter <var>sPart</var>
+
+ @param nPartLength
+ the length of the ascii value <var>pPartIdentifier</var>
+
+ @param rPartValue
+ returns the part value if <var>sPart</var> was split successfully
+
+ @param rPartArguments
+ returns the part arguments if <var>sPart</var> was split successfully
+
+ @return <TRUE/> if the identifier could be found and the string was split.
+ <FALSE/> otherwise.
+*/
+bool JobURL::implst_split( /*IN*/ std::u16string_view sPart ,
+ /*IN*/ const char* pPartIdentifier ,
+ /*IN*/ sal_Int32 nPartLength ,
+ /*OUT*/ OUString& rPartValue ,
+ /*OUT*/ OUString& rPartArguments )
+{
+ // first search for the given identifier
+ bool bPartFound = o3tl::matchIgnoreAsciiCase(sPart, std::string_view(pPartIdentifier,nPartLength));
+
+ // If it exist - we can split the part and return sal_True.
+ // Otherwise we do nothing and return sal_False.
+ if (bPartFound)
+ {
+ // But may the part has optional arguments - separated by a "?".
+ // Do so - we set the return value with the whole part string.
+ // Arguments will be set to an empty string as default.
+ // If we detect the right sign - we split the arguments and overwrite the default.
+ std::u16string_view sValueAndArguments = sPart.substr(nPartLength);
+ std::u16string_view sValue = sValueAndArguments;
+ OUString sArguments;
+
+ size_t nArgStart = sValueAndArguments.find('?');
+ if (nArgStart != std::u16string_view::npos)
+ {
+ sValue = sValueAndArguments.substr(0,nArgStart);
+ ++nArgStart; // ignore '?'!
+ sArguments = sValueAndArguments.substr(nArgStart);
+ }
+
+ rPartValue = sValue;
+ rPartArguments = sArguments;
+ }
+
+ return bPartFound;
+}
+
+} // namespace framework
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/framework/source/jobs/shelljob.cxx b/framework/source/jobs/shelljob.cxx
new file mode 100644
index 0000000000..0c895db33f
--- /dev/null
+++ b/framework/source/jobs/shelljob.cxx
@@ -0,0 +1,168 @@
+/* -*- 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 own header
+
+#include <jobs/shelljob.hxx>
+#include <jobs/jobconst.hxx>
+#include <services.h>
+
+// include others
+
+#include <osl/process.h>
+#include <comphelper/sequenceashashmap.hxx>
+
+// include interfaces
+
+#include <com/sun/star/util/PathSubstitution.hpp>
+#include <com/sun/star/util/XStringSubstitution.hpp>
+#include <cppuhelper/supportsservice.hxx>
+#include <utility>
+
+namespace framework{
+
+
+// XInterface, XTypeProvider, XServiceInfo
+
+OUString SAL_CALL ShellJob::getImplementationName()
+{
+ return "com.sun.star.comp.framework.ShellJob";
+}
+
+sal_Bool SAL_CALL ShellJob::supportsService( const OUString& sServiceName )
+{
+ return cppu::supportsService(this, sServiceName);
+}
+
+css::uno::Sequence< OUString > SAL_CALL ShellJob::getSupportedServiceNames()
+{
+ return { SERVICENAME_JOB };
+}
+
+
+ShellJob::ShellJob(css::uno::Reference< css::uno::XComponentContext > xContext)
+ : m_xContext (std::move(xContext))
+{
+}
+
+ShellJob::~ShellJob()
+{
+}
+
+css::uno::Any SAL_CALL ShellJob::execute(const css::uno::Sequence< css::beans::NamedValue >& lJobArguments)
+{
+ ::comphelper::SequenceAsHashMap lArgs (lJobArguments);
+ /** address job configuration inside argument set provided on method execute(). */
+ ::comphelper::SequenceAsHashMap lOwnCfg(lArgs.getUnpackedValueOrDefault("JobConfig", css::uno::Sequence< css::beans::NamedValue >()));
+
+ const OUString sCommand = lOwnCfg.getUnpackedValueOrDefault("Command" , OUString());
+ const css::uno::Sequence< OUString > lCommandArguments = lOwnCfg.getUnpackedValueOrDefault("Arguments" , css::uno::Sequence< OUString >());
+ const bool bDeactivateJobIfDone = lOwnCfg.getUnpackedValueOrDefault("DeactivateJobIfDone" , true );
+ const bool bCheckExitCode = lOwnCfg.getUnpackedValueOrDefault("CheckExitCode" , true );
+
+ // replace all might existing place holder.
+ OUString sRealCommand = impl_substituteCommandVariables(sCommand);
+
+ // Command is required as minimum.
+ // If it does not exists ... we can't do our job.
+ // Deactivate such miss configured job silently .-)
+ if (sRealCommand.isEmpty())
+ return ShellJob::impl_generateAnswer4Deactivation();
+
+ // do it
+ bool bDone = impl_execute(sRealCommand, lCommandArguments, bCheckExitCode);
+ if (! bDone)
+ return css::uno::Any();
+
+ // Job was done ... user configured deactivation of this job
+ // in such case.
+ if (bDeactivateJobIfDone)
+ return ShellJob::impl_generateAnswer4Deactivation();
+
+ // There was no decision about deactivation of this job.
+ // So we have to return nothing here !
+ return css::uno::Any();
+}
+
+css::uno::Any ShellJob::impl_generateAnswer4Deactivation()
+{
+ css::uno::Sequence< css::beans::NamedValue > aAnswer { { JobConst::ANSWER_DEACTIVATE_JOB, css::uno::Any(true) } };
+ return css::uno::Any(aAnswer);
+}
+
+OUString ShellJob::impl_substituteCommandVariables(const OUString& sCommand)
+{
+ try
+ {
+ css::uno::Reference< css::util::XStringSubstitution > xSubst( css::util::PathSubstitution::create(m_xContext) );
+ const bool bSubstRequired = true;
+ const OUString sCompleteCommand = xSubst->substituteVariables(sCommand, bSubstRequired);
+
+ return sCompleteCommand;
+ }
+ catch(const css::uno::Exception&)
+ {}
+
+ return OUString();
+}
+
+bool ShellJob::impl_execute(const OUString& sCommand ,
+ const css::uno::Sequence< OUString >& lArguments ,
+ bool bCheckExitCode)
+{
+ ::rtl_uString** pArgs = nullptr;
+ const ::sal_Int32 nArgs = lArguments.getLength ();
+ oslProcess hProcess(nullptr);
+
+ if (nArgs > 0)
+ pArgs = reinterpret_cast< ::rtl_uString** >(const_cast< OUString* >(lArguments.getConstArray()));
+
+ oslProcessError eError = osl_executeProcess(sCommand.pData, pArgs, nArgs, osl_Process_WAIT, nullptr, nullptr, nullptr, 0, &hProcess);
+
+ // executable not found or couldn't be started
+ if (eError != osl_Process_E_None)
+ return false;
+
+ bool bRet = true;
+ if (bCheckExitCode)
+ {
+ // check its return codes ...
+ oslProcessInfo aInfo;
+ aInfo.Size = sizeof (oslProcessInfo);
+ eError = osl_getProcessInfo(hProcess, osl_Process_EXITCODE, &aInfo);
+
+ if (eError != osl_Process_E_None)
+ bRet = false;
+ else
+ bRet = (aInfo.Code == 0);
+ }
+ osl_freeProcessHandle(hProcess);
+ return bRet;
+}
+
+} // namespace framework
+
+extern "C" SAL_DLLPUBLIC_EXPORT css::uno::XInterface*
+framework_ShellJob_get_implementation(
+ css::uno::XComponentContext* context, css::uno::Sequence<css::uno::Any> const& )
+{
+ return cppu::acquire(new framework::ShellJob(context));
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */