In this part we are going to see a more flexible alternative to ProtocolHandler component we say in Part6.
A job in LO is a UNO component that can be executed by the job execution environment upon an event. The job is protected from office termination.
The job component must implement the service com.sun.star.task.Job
if it needs to block the thread in which it is executed or implement com.sun.star.task.AsyncJob
if needed to be executed in a separate thread. If a job component implements both of these services, then the synchronous service is preferred by the job execution environment. Note that synchronous jobs should not display any user interfaces from it as repaint errors and threading issues will occur. In this part we will focus on implementing AsyncJob
service.
The AsyncJob
service supports the interface com::sun::star::task::XAsyncJob
which has only one method executeAsync()
with the following signature :
void executeAsync(
[in] sequence< com::sun::star::beans::NamedValue > Arguments,
[in] XJobListener Listener)
raises( com::sun::star::lang::IllegalArgumentException );
The Arguments
paramter is a sequence of NamedValue
structs. This contains information on the environment where the job is running, ie it tells if the job was created by the job executor or the dispatch framework or global event broadcaster service and possibly provides a document model or frame to work with.
As we are already familiar with writing components, we focus on just writing the executeAsync()
method and methods supporting it.
Lets declare our class AsyncJobImpl
which implements XAsyncJob
interface.
#define IMPLEMENTATION_NAME "inco.niocs.test.AsyncJobImpl"
class AsyncJobImpl : public cppu::WeakImplHelper2
<
com::sun::star::task::XAsyncJob,
com::sun::star::lang::XServiceInfo
>
{
private:
::com::sun::star::uno::Reference< ::com::sun::star::uno::XComponentContext > mxContext;
public:
AsyncJobImpl( const ::com::sun::star::uno::Reference< ::com::sun::star::uno::XComponentContext > &rxContext )
: mxContext( rxContext )
{
printf("DEBUG>>> Created AsyncJobImpl object : %p\n", this); fflush(stdout);
}
// XAsyncJob methods
virtual void SAL_CALL executeAsync( const ::com::sun::star::uno::Sequence< ::com::sun::star::beans::NamedValue >& rArgs,
const ::com::sun::star::uno::Reference< ::com::sun::star::task::XJobListener >& rxListener )
throw(::com::sun::star::lang::IllegalArgumentException,
::com::sun::star::uno::RuntimeException) override;
// XServiceInfo methods
virtual ::rtl::OUString SAL_CALL getImplementationName()
throw (::com::sun::star::uno::RuntimeException);
virtual sal_Bool SAL_CALL supportsService( const ::rtl::OUString& aServiceName )
throw (::com::sun::star::uno::RuntimeException);
virtual ::com::sun::star::uno::Sequence< ::rtl::OUString > SAL_CALL getSupportedServiceNames()
throw (::com::sun::star::uno::RuntimeException);
// A struct to store some job related info when executeAsync() is called
struct AsyncJobImplInfo
{
::rtl::OUString aEnvType;
::rtl::OUString aEventName;
::rtl::OUString aAlias;
::com::sun::star::uno::Reference< ::com::sun::star::frame::XFrame > xFrame;
::rtl::OUString aGenericConfigList, aJobConfigList, aEnvironmentList, aDynamicDataList;
};
private:
::rtl::OUString validateGetInfo( const ::com::sun::star::uno::Sequence< ::com::sun::star::beans::NamedValue >& rArgs,
const ::com::sun::star::uno::Reference< ::com::sun::star::task::XJobListener >& rxListener,
AsyncJobImplInfo& rJobInfo );
void formatOutArgs( const ::com::sun::star::uno::Sequence< ::com::sun::star::beans::NamedValue >& rList,
const ::rtl::OUString aListName,
::rtl::OUString& rFormattedList );
void writeInfoToSheet( const AsyncJobImplInfo& rJobInfo );
};
Note that we declared three private methods which help us in implementing executeAsync()
interface method.
validateGetInfo()
takes the arguments received from executeAsync()
and validates it and extracts needed information and fills it into a struct AsyncJobImplInfo
which has the following fields :
executeAsync()
for us to manipulate the UI/Model.executeAsync()
's with name Config
.executeAsync()
's with name JobConfig
.executeAsync()
's with name Environment
.executeAsync()
's with name DynamicData
.formatOutArgs()
takes a sequence of NamedValue
and stringifies it. writeInfoToSheet()
takes the info struct object AsyncJobImplInfo
and writes all info to Sheet1
of the current Frame.
executeAsync()
methods also has to call the method jobFinished()
of the XJobListener
object it received just before exiting the function.
Following are the implementations of these methods :
// XAsyncJob method implementations
void SAL_CALL AsyncJobImpl::executeAsync( const Sequence<NamedValue>& rArgs,
const Reference<XJobListener>& rxListener )
throw(IllegalArgumentException, RuntimeException)
{
printf("DEBUG>>> Called executeAsync() : this = %p\n", this); fflush(stdout);
AsyncJobImplInfo aJobInfo;
OUString aErr = validateGetInfo( rArgs, rxListener, aJobInfo );
if ( !aErr.isEmpty() )
{
sal_Int16 nArgPos = 0;
if ( aErr.startsWith( "Listener" ) )
nArgPos = 1;
throw IllegalArgumentException(
aErr,
// resolve to XInterface reference:
static_cast< ::cppu::OWeakObject * >(this),
nArgPos ); // argument pos
}
writeInfoToSheet( aJobInfo );
bool bIsDispatch = aJobInfo.aEnvType.equalsAscii("DISPATCH");
Sequence<NamedValue> aReturn( ( bIsDispatch ? 1 : 0 ) );
if ( bIsDispatch )
{
aReturn[0].Name = "SendDispatchResult";
DispatchResultEvent aResultEvent;
aResultEvent.Source = (cppu::OWeakObject*)this;
aResultEvent.State = DispatchResultState::SUCCESS;
aResultEvent.Result <<= true;
aReturn[0].Value <<= aResultEvent;
}
rxListener->jobFinished( Reference<com::sun::star::task::XAsyncJob>(this), makeAny(aReturn));
}
OUString AsyncJobImpl::validateGetInfo( const Sequence<NamedValue>& rArgs,
const Reference<XJobListener>& rxListener,
AsyncJobImpl::AsyncJobImplInfo& rJobInfo )
{
if ( !rxListener.is() )
return "Listener : invalid listener";
// Extract all sublists from rArgs.
Sequence<NamedValue> aGenericConfig;
Sequence<NamedValue> aJobConfig;
Sequence<NamedValue> aEnvironment;
Sequence<NamedValue> aDynamicData;
sal_Int32 nNumNVs = rArgs.getLength();
for ( sal_Int32 nIdx = 0; nIdx < nNumNVs; ++nIdx )
{
if ( rArgs[nIdx].Name.equalsAscii("Config") )
{
rArgs[nIdx].Value >>= aGenericConfig;
formatOutArgs( aGenericConfig, "Config", rJobInfo.aGenericConfigList );
}
else if ( rArgs[nIdx].Name.equalsAscii("JobConfig") )
{
rArgs[nIdx].Value >>= aJobConfig;
formatOutArgs( aJobConfig, "JobConfig", rJobInfo.aJobConfigList );
}
else if ( rArgs[nIdx].Name.equalsAscii("Environment") )
{
rArgs[nIdx].Value >>= aEnvironment;
formatOutArgs( aEnvironment, "Environment", rJobInfo.aEnvironmentList );
}
else if ( rArgs[nIdx].Name.equalsAscii("DynamicData") )
{
rArgs[nIdx].Value >>= aDynamicData;
formatOutArgs( aDynamicData, "DynamicData", rJobInfo.aDynamicDataList );
}
}
// Analyze the environment info. This sub list is the only guaranteed one!
if ( !aEnvironment.hasElements() )
return "Args : no environment";
sal_Int32 nNumEnvEntries = aEnvironment.getLength();
for ( sal_Int32 nIdx = 0; nIdx < nNumEnvEntries; ++nIdx )
{
if ( aEnvironment[nIdx].Name.equalsAscii("EnvType") )
aEnvironment[nIdx].Value >>= rJobInfo.aEnvType;
else if ( aEnvironment[nIdx].Name.equalsAscii("EventName") )
aEnvironment[nIdx].Value >>= rJobInfo.aEventName;
else if ( aEnvironment[nIdx].Name.equalsAscii("Frame") )
aEnvironment[nIdx].Value >>= rJobInfo.xFrame;
}
// Further the environment property "EnvType" is required as minimum.
if ( rJobInfo.aEnvType.isEmpty() ||
( ( !rJobInfo.aEnvType.equalsAscii("EXECUTOR") ) &&
( !rJobInfo.aEnvType.equalsAscii("DISPATCH") )
) )
return OUString("Args : \"" + rJobInfo.aEnvType + "\" isn't a valid value for EnvType");
// Analyze the set of shared config data.
if ( aGenericConfig.hasElements() )
{
sal_Int32 nNumGenCfgEntries = aGenericConfig.getLength();
for ( sal_Int32 nIdx = 0; nIdx < nNumGenCfgEntries; ++nIdx )
if ( aGenericConfig[nIdx].Name.equalsAscii("Alias") )
aGenericConfig[nIdx].Value >>= rJobInfo.aAlias;
}
return "";
}
void AsyncJobImpl::formatOutArgs( const Sequence<NamedValue>& rList,
const OUString aListName,
OUString& rFormattedList )
{
OUStringBuffer aBuf( 512 );
aBuf.append("List \"" + aListName + "\" : ");
if ( !rList.hasElements() )
aBuf.append("0 items");
else
{
sal_Int32 nNumEntries = rList.getLength();
aBuf.append(nNumEntries).append(" item(s) : { ");
for ( sal_Int32 nIdx = 0; nIdx < nNumEntries; ++nIdx )
{
OUString aVal;
rList[nIdx].Value >>= aVal;
aBuf.append("\"" + rList[nIdx].Name + "\" : \"" + aVal + "\", ");
}
aBuf.append(" }");
}
rFormattedList = aBuf.toString();
}
void WriteStringsToSheet( const Reference< XFrame > &rxFrame, const std::vector<OUString>& rStrings )
{
if ( !rxFrame.is() )
return;
Reference< XController > xCtrl = rxFrame->getController();
if ( !xCtrl.is() )
return;
Reference< XModel > xModel = xCtrl->getModel();
if ( !xModel.is() )
return;
Reference< XSpreadsheetDocument > xSpreadsheetDocument( xModel, UNO_QUERY );
if ( !xSpreadsheetDocument.is() )
return;
Reference< XSpreadsheets > xSpreadsheets = xSpreadsheetDocument->getSheets();
if ( !xSpreadsheets->hasByName("Sheet1") )
return;
Any aSheet = xSpreadsheets->getByName("Sheet1");
Reference< XSpreadsheet > xSpreadsheet( aSheet, UNO_QUERY );
size_t nNumStrings = rStrings.size();
for ( size_t nIdx = 0; nIdx < nNumStrings; ++nIdx )
{
Reference< XCell > xCell = xSpreadsheet->getCellByPosition(0, nIdx);
xCell->setFormula(rStrings[nIdx]);
printf("DEBUG>>> Wrote \"%s\" to Cell A%d\n",
OUStringToOString( rStrings[nIdx], RTL_TEXTENCODING_ASCII_US ).getStr(), nIdx); fflush(stdout);
}
}
void AsyncJobImpl::writeInfoToSheet( const AsyncJobImpl::AsyncJobImplInfo& rJobInfo )
{
if ( !rJobInfo.xFrame.is() )
{
printf("DEBUG>>> Frame passed is null, cannot write to sheet !\n");
fflush(stdout);
}
std::vector<OUString> aStrList = {
"EnvType = " + rJobInfo.aEnvType,
"EventName = " + rJobInfo.aEventName,
"Alias = " + rJobInfo.aAlias,
rJobInfo.aGenericConfigList,
rJobInfo.aJobConfigList,
rJobInfo.aEnvironmentList,
rJobInfo.aDynamicDataList
};
WriteStringsToSheet( rJobInfo.xFrame, aStrList );
}
A Job component can be called from the menu or toolbar buttons using any of the three following methods :
vnd.sun.star.job:event=<EVENTNAME>
, where EVENTNAME
can be any of the standard events or a custom event registered by the Job component.vnd.sun.star.job:alias=<ALIAS_OF_THE_JOB>
.vnd.sun.star.job:service=<IMPLEMENTAION_NAME>
example : vnd.sun.star.job:service=inco.niocs.test.AsyncJobImpl
.We specify our job's alias, attached events in an xml file called Job.xcu
.
<oor:component-data oor:name="Jobs" oor:package="org.openoffice.Office" xmlns:oor="http://openoffice.org/2001/registry" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<node oor:name="Jobs">
<!-- vvvvvvvvvvv AsyncJobImpl is the alias -->
<node oor:name="AsyncJobImpl" oor:op="replace">
<prop oor:name="Service" oor:type="xs:string">
<value>inco.niocs.test.AsyncJobImpl</value>
</prop>
</node>
</node>
<node oor:name="Events">
<!--
<node oor:name="onFirstVisibleTask" oor:op="modify">
<node oor:name="JobList">
<node oor:name="AsyncJobImpl" oor:op="replace"/>
</node>
</node>
-->
<node oor:name="onMyOwnJobEvent" oor:op="replace">
<node oor:name="JobList">
<node oor:name="AsyncJobImpl" oor:op="replace"/>
</node>
</node>
</node>
</oor:component-data>
We now add menu entries to call our job in the three different ways discussed before.
<oor:component-data xmlns:oor="http://openoffice.org/2001/registry" xmlns:xs="http://www.w3.org/2001/XMLSchema" oor:name="Addons" oor:package="org.openoffice.Office">
<node oor:name="AddonUI">
<node oor:name="OfficeMenuBar">
<node oor:name="inco.niocs.test.asyncjobimpl.menu" oor:op="replace">
<prop oor:name="Title" oor:type="xs:string">
<value/>
<value xml:lang="en-US">Add-On example</value>
</prop>
<node oor:name="Submenu">
<node oor:name="m1" oor:op="replace">
<prop oor:name="URL" oor:type="xs:string">
<value>vnd.sun.star.job:alias=AsyncJobImpl</value>
</prop>
<prop oor:name="Title" oor:type="xs:string">
<value xml:lang="en-US">AsyncJob (ALIAS)</value>
</prop>
<prop oor:name="Target" oor:type="xs:string">
<value>_self</value>
</prop>
<prop oor:name="Context" oor:type="xs:string">
<value>com.sun.star.sheet.SpreadsheetDocument</value>
</prop>
</node>
<node oor:name="m2" oor:op="replace">
<prop oor:name="URL" oor:type="xs:string">
<value>vnd.sun.star.job:event=onMyOwnJobEvent</value>
</prop>
<prop oor:name="Title" oor:type="xs:string">
<value xml:lang="en-US">AsyncJob (EVENT)</value>
</prop>
<prop oor:name="Target" oor:type="xs:string">
<value>_self</value>
</prop>
<prop oor:name="Context" oor:type="xs:string">
<value>com.sun.star.sheet.SpreadsheetDocument</value>
</prop>
</node>
<node oor:name="m3" oor:op="replace">
<prop oor:name="URL" oor:type="xs:string">
<value>vnd.sun.star.job:service=inco.niocs.test.AsyncJobImpl</value>
</prop>
<prop oor:name="Title" oor:type="xs:string">
<value xml:lang="en-US">AsyncJob (SERVICE)</value>
</prop>
<prop oor:name="Target" oor:type="xs:string">
<value>_self</value>
</prop>
<prop oor:name="Context" oor:type="xs:string">
<value>com.sun.star.sheet.SpreadsheetDocument</value>
</prop>
</node>
</node>
</node>
</node>
</node>
</oor:component-data>
Please remember to remove any previous extensions from Part5/Part6 using $LOROOT/instdir/program/unopkg remove
The whole project can be downloaded and built by doing :
$ git clone https://github.com/niocs/AsyncJobExtension.git
$ cd AsyncJobExtension
$ make
$ $LOROOT/instdir/program/unopkg add /home/$username/libreoffice5.3_sdk/LINUXexample.out/bin/AsyncJobExtension.oxt
To remove the extension, do :
$ $LOROOT/instdir/program/unopkg remove AsyncJobExtension.oxt
On running Calc you will notice there are three menu entries inside the menu Add-On example
each for triggering our component via three methods :
On clicking each of them, Sheet1's column A will be filled with job environment information. Depending on the menu entry you click, some of the content will be blank.
You will also notice the debug info printed on the terminal on each click. It would look something like :
DEBUG>>> Created AsyncJobImpl object : 0x3e78f10
DEBUG>>> Called executeAsync() : this = 0x3e78f10
DEBUG>>> Wrote "EnvType = DISPATCH" to Cell A0
DEBUG>>> Wrote "EventName = " to Cell A1
DEBUG>>> Wrote "Alias = AsyncJobImpl" to Cell A2
DEBUG>>> Wrote "List "Config" : 3 item(s) : { "Alias" : "AsyncJobImpl", "Service" : "inco.niocs.test.AsyncJobImpl", "Context" : "", }" to Cell A3
DEBUG>>> Wrote "" to Cell A4
DEBUG>>> Wrote "List "Environment" : 2 item(s) : { "EnvType" : "DISPATCH", "Frame" : "", }" to Cell A5
DEBUG>>> Wrote "List "DynamicData" : 1 item(s) : { "Referer" : "private:user", }" to Cell A6
DEBUG>>> Created AsyncJobImpl object : 0x4772690
DEBUG>>> Called executeAsync() : this = 0x4772690
DEBUG>>> Wrote "EnvType = DISPATCH" to Cell A0
DEBUG>>> Wrote "EventName = onMyOwnJobEvent" to Cell A1
DEBUG>>> Wrote "Alias = " to Cell A2
DEBUG>>> Wrote "" to Cell A3
DEBUG>>> Wrote "" to Cell A4
DEBUG>>> Wrote "List "Environment" : 3 item(s) : { "EnvType" : "DISPATCH", "Frame" : "", "EventName" : "onMyOwnJobEvent", }" to Cell A5
DEBUG>>> Wrote "List "DynamicData" : 1 item(s) : { "Referer" : "private:user", }" to Cell A6
DEBUG>>> Created AsyncJobImpl object : 0x4780a70
DEBUG>>> Called executeAsync() : this = 0x4780a70
DEBUG>>> Wrote "EnvType = DISPATCH" to Cell A0
DEBUG>>> Wrote "EventName = " to Cell A1
DEBUG>>> Wrote "Alias = " to Cell A2
DEBUG>>> Wrote "" to Cell A3
DEBUG>>> Wrote "" to Cell A4
DEBUG>>> Wrote "List "Environment" : 2 item(s) : { "EnvType" : "DISPATCH", "Frame" : "", }" to Cell A5
DEBUG>>> Wrote "List "DynamicData" : 1 item(s) : { "Referer" : "private:user", }" to Cell A6
NOTE : From the above we can note that each time a event/trigger is made our class AsyncJobImpl
is instantiated.
Please remember to remove any previous extension from Part5/Part6 using unopkg remove
.
Download the extension as :
$ git clone https://github.com/niocs/AsyncJobExtension.git
$ cd AsyncJobExtension
In AsyncJobExtension
directory you will see a file called AsyncJobExtension.oxt
. This is the prebuilt extension. Install this extension using
$ unopkg add /path/to/AsyncJobExtension.oxt
To remove this extension do :
$ unopkg remove AsyncJobExtension.oxt
On running Calc you will notice there are three menu entries inside the menu Add-On example
each for triggering our component via three methods :
On clicking each of them, Sheet1's column A will be filled with job environment information. Depending on the menu entry you click, some of the content will be blank.