diff options
Diffstat (limited to '')
-rw-r--r-- | framework/source/jobs/helponstartup.cxx | 336 | ||||
-rw-r--r-- | framework/source/jobs/job.cxx | 857 | ||||
-rw-r--r-- | framework/source/jobs/jobdata.cxx | 563 | ||||
-rw-r--r-- | framework/source/jobs/jobdispatch.cxx | 496 | ||||
-rw-r--r-- | framework/source/jobs/jobexecutor.cxx | 399 | ||||
-rw-r--r-- | framework/source/jobs/jobresult.cxx | 192 | ||||
-rw-r--r-- | framework/source/jobs/joburl.cxx | 253 | ||||
-rw-r--r-- | framework/source/jobs/shelljob.cxx | 168 |
8 files changed, 3264 insertions, 0 deletions
diff --git a/framework/source/jobs/helponstartup.cxx b/framework/source/jobs/helponstartup.cxx new file mode 100644 index 000000000..2795a3f45 --- /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 000000000..726c037cf --- /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 <tools/diagnose_ex.h> +#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 000000000..863bc8c4b --- /dev/null +++ b/framework/source/jobs/jobdata.cxx @@ -0,0 +1,563 @@ +/* -*- 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 ) +{ + SolarMutexGuard g; + // 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 ) +{ + SolarMutexGuard g; + // 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 ) +{ + SolarMutexGuard g; + // 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); + + SolarMutexGuard g; + // 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 ) +{ + SolarMutexGuard g; + + // 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 ) +{ + SolarMutexGuard g; + 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 +{ + SolarMutexGuard g; + return m_eMode; +} + +JobData::EEnvironment JobData::getEnvironment() const +{ + SolarMutexGuard g; + return m_eEnvironment; +} + +OUString JobData::getEnvironmentDescriptor() const +{ + OUString sDescriptor; + SolarMutexGuard g; + 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 +{ + SolarMutexGuard g; + return m_sService; +} + +OUString JobData::getEvent() const +{ + SolarMutexGuard g; + return m_sEvent; +} + +std::vector< css::beans::NamedValue > JobData::getJobConfig() const +{ + SolarMutexGuard g; + return m_lArguments; +} + +css::uno::Sequence< css::beans::NamedValue > JobData::getConfig() const +{ + SolarMutexGuard g; + 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 +{ + SolarMutexGuard g; + 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() +{ + SolarMutexGuard g; + + // 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(const OUString& rModuleIdent) const +{ + sal_Int32 nContextLen = m_sContext.getLength(); + sal_Int32 nModuleIdLen = rModuleIdent.getLength(); + + 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() +{ + SolarMutexGuard g; + 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 000000000..d1e227b9a --- /dev/null +++ b/framework/source/jobs/jobdispatch.cxx @@ -0,0 +1,496 @@ +/* -*- 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. + /* SAFE { */ + SolarMutexResettableGuard aReadLock; + std::vector< OUString > lJobs = JobData::getEnabledJobsForEvent(m_xContext, sEvent); + aReadLock.clear(); + /* } SAFE */ + + 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) + { + /* SAFE { */ + aReadLock.reset(); + + 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); + + aReadLock.clear(); + /* } SAFE */ + + 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 ) +{ + /* SAFE { */ + SolarMutexClearableGuard aReadLock; + + 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); + + aReadLock.clear(); + /* } SAFE */ + + 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 ) +{ + /* SAFE { */ + SolarMutexClearableGuard aReadLock; + + 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); + + aReadLock.clear(); + /* } SAFE */ + + 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 000000000..5578b877b --- /dev/null +++ b/framework/source/jobs/jobexecutor.cxx @@ -0,0 +1,399 @@ +/* -*- 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 <cppuhelper/basemutex.hxx> +#include <cppuhelper/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 cppu::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 : private cppu::BaseMutex, 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 SAL_CALL disposing() 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 ) + : Base (m_aMutex) + , 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() +{ + disposing(); +} + +void JobExecutor::disposing() { + css::uno::Reference<css::container::XContainer> notifier; + css::uno::Reference<css::container::XContainerListener> listener; + { + osl::MutexGuard g(rBHelper.rMutex); + 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()"); + + std::vector< OUString > lJobs; + + /* SAFE */ { + osl::MutexGuard g(rBHelper.rMutex); + + // 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; + + // 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. + lJobs = JobData::getEnabledJobsForEvent(m_xContext, sEvent); + } /* SAFE */ + + // step over all enabled jobs and execute it + size_t c = lJobs.size(); + for (size_t j=0; j<c; ++j) + { + rtl::Reference<Job> pJob; + + /* SAFE */ + { + SolarMutexGuard g2; + + 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. + */ + pJob = new Job(m_xContext, css::uno::Reference< css::frame::XFrame >()); + pJob->setJobData(aCfg); + } /* SAFE */ + + pJob->execute(css::uno::Sequence< css::beans::NamedValue >()); + } +} + +void SAL_CALL JobExecutor::notifyEvent( const css::document::EventObject& aEvent ) +{ + static constexpr OUStringLiteral EVENT_ON_DOCUMENT_OPENED(u"onDocumentOpened"); // Job UI event : OnNew or OnLoad + static constexpr OUStringLiteral EVENT_ON_DOCUMENT_ADDED(u"onDocumentAdded"); // Job API event : OnCreate or OnLoadFinished + + OUString aModuleIdentifier; + ::std::vector< JobData::TJob2DocEventBinding > lJobs; + + /* SAFE */ { + osl::MutexGuard g(rBHelper.rMutex); + + // 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& ) + {} + + // 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; + + /* SAFE */ { + SolarMutexGuard g2; + + 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); + } /* SAFE */ + + 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 { */ + osl::MutexGuard g(rBHelper.rMutex); + 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 000000000..58cd2b594 --- /dev/null +++ b/framework/source/jobs/jobresult.cxx @@ -0,0 +1,192 @@ +/* -*- 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) +{ + SolarMutexGuard g; + 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 +{ + SolarMutexGuard g; + 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 +{ + SolarMutexGuard g; + return m_lArguments; +} + +css::frame::DispatchResultEvent JobResult::getDispatchResult() const +{ + SolarMutexGuard g; + 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 000000000..efff201a8 --- /dev/null +++ b/framework/source/jobs/joburl.cxx @@ -0,0 +1,253 @@ +/* -*- 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> + +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 +{ + SolarMutexGuard g; + 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 +{ + SolarMutexGuard g; + + 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 +{ + SolarMutexGuard g; + + 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 +{ + SolarMutexGuard g; + + 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*/ const OUString& sPart , + /*IN*/ const char* pPartIdentifier , + /*IN*/ sal_Int32 nPartLength , + /*OUT*/ OUString& rPartValue , + /*OUT*/ OUString& rPartArguments ) +{ + // first search for the given identifier + bool bPartFound = sPart.matchIgnoreAsciiCaseAsciiL(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. + OUString sValueAndArguments = sPart.copy(nPartLength); + OUString sValue = sValueAndArguments; + OUString sArguments; + + sal_Int32 nArgStart = sValueAndArguments.indexOf('?'); + if (nArgStart!=-1) + { + sValue = sValueAndArguments.copy(0,nArgStart); + ++nArgStart; // ignore '?'! + sArguments = sValueAndArguments.copy(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 000000000..0c895db33 --- /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: */ |