xref: /core/ucb/source/ucp/cmis/cmis_content.cxx (revision a3fe1c6f)
1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2 /*
3  * This file is part of the LibreOffice project.
4  *
5  * This Source Code Form is subject to the terms of the Mozilla Public
6  * License, v. 2.0. If a copy of the MPL was not distributed with this
7  * file, You can obtain one at http://mozilla.org/MPL/2.0/.
8  */
9 
10 #include <string_view>
11 
12 #include <boost/make_shared.hpp>
13 
14 #include <com/sun/star/beans/IllegalTypeException.hpp>
15 #include <com/sun/star/beans/PropertyAttribute.hpp>
16 #include <com/sun/star/beans/PropertyValue.hpp>
17 #include <com/sun/star/beans/XPropertySetInfo.hpp>
18 #include <com/sun/star/document/CmisProperty.hpp>
19 #include <com/sun/star/io/XActiveDataSink.hpp>
20 #include <com/sun/star/io/XActiveDataStreamer.hpp>
21 #include <com/sun/star/lang/IllegalAccessException.hpp>
22 #include <com/sun/star/lang/IllegalArgumentException.hpp>
23 #include <com/sun/star/lang/WrappedTargetRuntimeException.hpp>
24 #include <com/sun/star/task/InteractionClassification.hpp>
25 #include <com/sun/star/ucb/ContentInfo.hpp>
26 #include <com/sun/star/ucb/ContentInfoAttribute.hpp>
27 #include <com/sun/star/ucb/InsertCommandArgument2.hpp>
28 #include <com/sun/star/ucb/InteractiveBadTransferURLException.hpp>
29 #include <com/sun/star/ucb/InteractiveAugmentedIOException.hpp>
30 #include <com/sun/star/ucb/InteractiveNetworkResolveNameException.hpp>
31 #include <com/sun/star/ucb/InteractiveNetworkConnectException.hpp>
32 #include <com/sun/star/ucb/InteractiveNetworkReadException.hpp>
33 #include <com/sun/star/ucb/MissingInputStreamException.hpp>
34 #include <com/sun/star/ucb/OpenMode.hpp>
35 #include <com/sun/star/ucb/UnsupportedCommandException.hpp>
36 #include <com/sun/star/ucb/UnsupportedDataSinkException.hpp>
37 #include <com/sun/star/ucb/UnsupportedOpenModeException.hpp>
38 #include <com/sun/star/ucb/XCommandInfo.hpp>
39 #include <com/sun/star/ucb/XDynamicResultSet.hpp>
40 
41 #include <comphelper/processfactory.hxx>
42 #include <comphelper/sequence.hxx>
43 #include <cppuhelper/exc_hlp.hxx>
44 #include <cppuhelper/queryinterface.hxx>
45 #include <config_oauth2.h>
46 #include <o3tl/runtimetooustring.hxx>
47 #include <sal/log.hxx>
48 #include <tools/urlobj.hxx>
49 #include <tools/long.hxx>
50 #include <ucbhelper/cancelcommandexecution.hxx>
51 #include <ucbhelper/content.hxx>
52 #include <ucbhelper/contentidentifier.hxx>
53 #include <ucbhelper/propertyvalueset.hxx>
54 #include <ucbhelper/proxydecider.hxx>
55 #include <ucbhelper/macros.hxx>
56 #include <sax/tools/converter.hxx>
57 #include <systools/curlinit.hxx>
58 
59 #include <utility>
60 
61 #include "auth_provider.hxx"
62 #include "cmis_content.hxx"
63 #include "cmis_provider.hxx"
64 #include "cmis_resultset.hxx"
65 #include "cmis_strings.hxx"
66 #include "std_inputstream.hxx"
67 #include "std_outputstream.hxx"
68 
69 #define OUSTR_TO_STDSTR(s) std::string( OUStringToOString( s, RTL_TEXTENCODING_UTF8 ) )
70 #define STD_TO_OUSTR( str ) OStringToOUString( str, RTL_TEXTENCODING_UTF8 )
71 
72 using namespace com::sun::star;
73 
74 namespace
75 {
lcl_boostToUnoTime(const boost::posix_time::ptime & boostTime)76     util::DateTime lcl_boostToUnoTime(const boost::posix_time::ptime& boostTime)
77     {
78         util::DateTime unoTime;
79         unoTime.Year = boostTime.date().year();
80         unoTime.Month = boostTime.date().month();
81         unoTime.Day = boostTime.date().day();
82         unoTime.Hours = boostTime.time_of_day().hours();
83         unoTime.Minutes = boostTime.time_of_day().minutes();
84         unoTime.Seconds = boostTime.time_of_day().seconds();
85 
86         // TODO FIXME maybe we should compile with BOOST_DATE_TIME_POSIX_TIME_STD_CONFIG
87         //            to actually get nanosecond precision in boostTime?
88         // use this way rather than total_nanos to avoid overflows with 32-bit long
89         const tools::Long ticks = boostTime.time_of_day().fractional_seconds();
90         tools::Long nanoSeconds = ticks * ( 1000000000 / boost::posix_time::time_duration::ticks_per_second());
91 
92         unoTime.NanoSeconds = nanoSeconds;
93 
94         return unoTime;
95     }
96 
lcl_cmisPropertyToUno(const libcmis::PropertyPtr & pProperty)97     uno::Any lcl_cmisPropertyToUno( const libcmis::PropertyPtr& pProperty )
98     {
99         uno::Any aValue;
100         switch ( pProperty->getPropertyType( )->getType( ) )
101         {
102             default:
103             case libcmis::PropertyType::String:
104                 {
105                     auto aCmisStrings = pProperty->getStrings( );
106                     uno::Sequence< OUString > aStrings( aCmisStrings.size( ) );
107                     OUString* aStringsArr = aStrings.getArray( );
108                     sal_Int32 i = 0;
109                     for ( const auto& rCmisStr : aCmisStrings )
110                     {
111                         aStringsArr[i++] = STD_TO_OUSTR( rCmisStr );
112                     }
113                     aValue <<= aStrings;
114                 }
115                 break;
116             case libcmis::PropertyType::Integer:
117                 {
118                     auto aCmisLongs = pProperty->getLongs( );
119                     uno::Sequence< sal_Int64 > aLongs( aCmisLongs.size( ) );
120                     sal_Int64* aLongsArr = aLongs.getArray( );
121                     sal_Int32 i = 0;
122                     for ( const auto& rCmisLong : aCmisLongs )
123                     {
124                         aLongsArr[i++] = rCmisLong;
125                     }
126                     aValue <<= aLongs;
127                 }
128                 break;
129             case libcmis::PropertyType::Decimal:
130                 {
131                     auto aCmisDoubles = pProperty->getDoubles( );
132                     uno::Sequence< double > aDoubles = comphelper::containerToSequence(aCmisDoubles);
133                     aValue <<= aDoubles;
134                 }
135                 break;
136             case libcmis::PropertyType::Bool:
137                 {
138                     auto aCmisBools = pProperty->getBools( );
139                     uno::Sequence< sal_Bool > aBools( aCmisBools.size( ) );
140                     sal_Bool* aBoolsArr = aBools.getArray( );
141                     sal_Int32 i = 0;
142                     for ( bool bCmisBool : aCmisBools )
143                     {
144                         aBoolsArr[i++] = bCmisBool;
145                     }
146                     aValue <<= aBools;
147                 }
148                 break;
149             case libcmis::PropertyType::DateTime:
150                 {
151                     auto aCmisTimes = pProperty->getDateTimes( );
152                     uno::Sequence< util::DateTime > aTimes( aCmisTimes.size( ) );
153                     util::DateTime* aTimesArr = aTimes.getArray( );
154                     sal_Int32 i = 0;
155                     for ( const auto& rCmisTime : aCmisTimes )
156                     {
157                         aTimesArr[i++] = lcl_boostToUnoTime( rCmisTime );
158                     }
159                     aValue <<= aTimes;
160                 }
161                 break;
162         }
163         return aValue;
164     }
165 
lcl_unoToCmisProperty(const document::CmisProperty & prop)166     libcmis::PropertyPtr lcl_unoToCmisProperty(const document::CmisProperty& prop )
167     {
168         libcmis::PropertyTypePtr propertyType( new libcmis::PropertyType( ) );
169 
170         OUString id = prop.Id;
171         OUString name = prop.Name;
172         bool bUpdatable = prop.Updatable;
173         bool bRequired = prop.Required;
174         bool bMultiValued = prop.MultiValued;
175         bool bOpenChoice = prop.OpenChoice;
176         uno::Any value = prop.Value;
177         std::vector< std::string > values;
178 
179         libcmis::PropertyType::Type type = libcmis::PropertyType::String;
180         if ( prop.Type == CMIS_TYPE_STRING )
181         {
182             uno::Sequence< OUString > seqValue;
183             value >>= seqValue;
184             std::transform(std::cbegin(seqValue), std::cend(seqValue), std::back_inserter(values),
185                 [](const OUString& rValue) -> std::string { return OUSTR_TO_STDSTR( rValue ); });
186             type = libcmis::PropertyType::String;
187         }
188         else if ( prop.Type == CMIS_TYPE_BOOL )
189         {
190             uno::Sequence< sal_Bool > seqValue;
191             value >>= seqValue;
192             std::transform(std::cbegin(seqValue), std::cend(seqValue), std::back_inserter(values),
193                 [](const bool nValue) -> std::string { return std::string( OString::boolean( nValue ) ); });
194             type = libcmis::PropertyType::Bool;
195         }
196         else if ( prop.Type == CMIS_TYPE_INTEGER )
197         {
198             uno::Sequence< sal_Int64 > seqValue;
199             value >>= seqValue;
200             std::transform(std::cbegin(seqValue), std::cend(seqValue), std::back_inserter(values),
201                 [](const sal_Int64 nValue) -> std::string { return std::string( OString::number( nValue ) ); });
202             type = libcmis::PropertyType::Integer;
203         }
204         else if ( prop.Type == CMIS_TYPE_DECIMAL )
205         {
206             uno::Sequence< double > seqValue;
207             value >>= seqValue;
208             std::transform(std::cbegin(seqValue), std::cend(seqValue), std::back_inserter(values),
209                 [](const double fValue) -> std::string { return std::string( OString::number( fValue ) ); });
210             type = libcmis::PropertyType::Decimal;
211         }
212         else if ( prop.Type == CMIS_TYPE_DATETIME )
213         {
214             uno::Sequence< util::DateTime > seqValue;
215             value >>= seqValue;
216             std::transform(std::cbegin(seqValue), std::cend(seqValue), std::back_inserter(values),
217                 [](const util::DateTime& rValue) -> std::string {
218                     OUStringBuffer aBuffer;
219                     ::sax::Converter::convertDateTime( aBuffer, rValue, nullptr );
220                     return OUSTR_TO_STDSTR( aBuffer );
221                 });
222             type = libcmis::PropertyType::DateTime;
223         }
224 
225         propertyType->setId( OUSTR_TO_STDSTR( id ));
226         propertyType->setDisplayName( OUSTR_TO_STDSTR( name ) );
227         propertyType->setUpdatable( bUpdatable );
228         propertyType->setRequired( bRequired );
229         propertyType->setMultiValued( bMultiValued );
230         propertyType->setOpenChoice( bOpenChoice );
231         propertyType->setType( type );
232 
233         libcmis::PropertyPtr property( new libcmis::Property( propertyType, std::move(values) ) );
234 
235         return property;
236     }
237 
generateErrorArguments(const cmis::URL & rURL)238     uno::Sequence< uno::Any > generateErrorArguments( const cmis::URL & rURL )
239     {
240         uno::Sequence< uno::Any > aArguments{ uno::Any(beans::PropertyValue(
241                                                            u"Binding URL"_ustr,
242                                                            - 1,
243                                                            uno::Any( rURL.getBindingUrl() ),
244                                                            beans::PropertyState_DIRECT_VALUE )),
245                                               uno::Any(beans::PropertyValue(
246                                                            u"Username"_ustr,
247                                                            -1,
248                                                            uno::Any( rURL.getUsername() ),
249                                                            beans::PropertyState_DIRECT_VALUE )),
250                                               uno::Any(beans::PropertyValue(
251                                                            u"Repository Id"_ustr,
252                                                            -1,
253                                                            uno::Any( rURL.getRepositoryId() ),
254                                                            beans::PropertyState_DIRECT_VALUE )) };
255 
256         return aArguments;
257     }
258 }
259 
260 namespace cmis
261 {
Content(const uno::Reference<uno::XComponentContext> & rxContext,ContentProvider * pProvider,const uno::Reference<ucb::XContentIdentifier> & Identifier,libcmis::ObjectPtr pObject)262     Content::Content( const uno::Reference< uno::XComponentContext >& rxContext,
263         ContentProvider *pProvider, const uno::Reference< ucb::XContentIdentifier >& Identifier,
264         libcmis::ObjectPtr pObject )
265         : ContentImplHelper( rxContext, pProvider, Identifier ),
266         m_pProvider( pProvider ),
267         m_pSession( nullptr ),
268         m_pObject(std::move( pObject )),
269         m_sURL( Identifier->getContentIdentifier( ) ),
270         m_aURL( m_sURL ),
271         m_bTransient( false ),
272         m_bIsFolder( false )
273     {
274         SAL_INFO( "ucb.ucp.cmis", "Content::Content() " << m_sURL );
275 
276         m_sObjectPath = m_aURL.getObjectPath( );
277         m_sObjectId = m_aURL.getObjectId( );
278     }
279 
Content(const uno::Reference<uno::XComponentContext> & rxContext,ContentProvider * pProvider,const uno::Reference<ucb::XContentIdentifier> & Identifier,bool bIsFolder)280     Content::Content( const uno::Reference< uno::XComponentContext >& rxContext, ContentProvider *pProvider,
281         const uno::Reference< ucb::XContentIdentifier >& Identifier,
282         bool bIsFolder )
283         : ContentImplHelper( rxContext, pProvider, Identifier ),
284         m_pProvider( pProvider ),
285         m_pSession( nullptr ),
286         m_sURL( Identifier->getContentIdentifier( ) ),
287         m_aURL( m_sURL ),
288         m_bTransient( true ),
289         m_bIsFolder( bIsFolder )
290     {
291         SAL_INFO( "ucb.ucp.cmis", "Content::Content() " << m_sURL );
292 
293         m_sObjectPath = m_aURL.getObjectPath( );
294         m_sObjectId = m_aURL.getObjectId( );
295     }
296 
~Content()297     Content::~Content()
298     {
299     }
300 
getSession(const uno::Reference<ucb::XCommandEnvironment> & xEnv)301     libcmis::Session* Content::getSession( const uno::Reference< ucb::XCommandEnvironment >& xEnv )
302     {
303         // Set the proxy if needed. We are doing that all times as the proxy data shouldn't be cached.
304         ucbhelper::InternetProxyDecider aProxyDecider( m_xContext );
305         INetURLObject aBindingUrl( m_aURL.getBindingUrl( ) );
306         const OUString sProxy = aProxyDecider.getProxy(
307                 INetURLObject::GetScheme( aBindingUrl.GetProtocol( ) ), aBindingUrl.GetHost(), aBindingUrl.GetPort() );
308         libcmis::SessionFactory::setProxySettings( OUSTR_TO_STDSTR( sProxy ), std::string(), std::string(), std::string() );
309 
310         // Look for a cached session, key is binding url + repo id
311         OUString sSessionId = m_aURL.getBindingUrl( ) + m_aURL.getRepositoryId( );
312         if ( nullptr == m_pSession )
313             m_pSession = m_pProvider->getSession( sSessionId, m_aURL.getUsername( ) );
314 
315         if ( nullptr == m_pSession )
316         {
317             // init libcurl callback
318             libcmis::SessionFactory::setCurlInitProtocolsFunction(&::InitCurl_easy);
319 
320             // Get the auth credentials
321             AuthProvider aAuthProvider(xEnv, m_xIdentifier->getContentIdentifier(), m_aURL.getBindingUrl());
322             AuthProvider::setXEnv( xEnv );
323 
324             auto rUsername = OUSTR_TO_STDSTR( m_aURL.getUsername( ) );
325             auto rPassword = OUSTR_TO_STDSTR( m_aURL.getPassword( ) );
326 
327             bool bSkipInitialPWAuth = false;
328             if (m_aURL.getBindingUrl() == ONEDRIVE_BASE_URL
329                 || m_aURL.getBindingUrl() == GDRIVE_BASE_URL)
330             {
331                 // skip the initial username and pw-auth prompt, the only supported method is the
332                 // auth-code-fallback one (login with your browser, copy code into the dialog)
333                 // TODO: if LO were to listen on localhost for the request, it would be much nicer
334                 // user experience
335                 bSkipInitialPWAuth = true;
336                 rPassword = aAuthProvider.getRefreshToken(rUsername);
337             }
338 
339             bool bIsDone = false;
340 
341             while ( !bIsDone )
342             {
343                 if (bSkipInitialPWAuth || aAuthProvider.authenticationQuery(rUsername, rPassword))
344                 {
345                     // Initiate a CMIS session and register it as we found nothing
346                     libcmis::OAuth2DataPtr oauth2Data;
347                     if ( m_aURL.getBindingUrl( ) == GDRIVE_BASE_URL )
348                     {
349                         // reset the skip, so user gets a chance to cancel
350                         bSkipInitialPWAuth = false;
351                         libcmis::SessionFactory::setOAuth2AuthCodeProvider(AuthProvider::copyWebAuthCodeFallback);
352                         oauth2Data = boost::make_shared<libcmis::OAuth2Data>(
353                             GDRIVE_AUTH_URL, GDRIVE_TOKEN_URL,
354                             GDRIVE_SCOPE, GDRIVE_REDIRECT_URI,
355                             GDRIVE_CLIENT_ID, GDRIVE_CLIENT_SECRET );
356                     }
357                     if ( m_aURL.getBindingUrl().startsWith( ALFRESCO_CLOUD_BASE_URL ) )
358                         oauth2Data = boost::make_shared<libcmis::OAuth2Data>(
359                             ALFRESCO_CLOUD_AUTH_URL, ALFRESCO_CLOUD_TOKEN_URL,
360                             ALFRESCO_CLOUD_SCOPE, ALFRESCO_CLOUD_REDIRECT_URI,
361                             ALFRESCO_CLOUD_CLIENT_ID, ALFRESCO_CLOUD_CLIENT_SECRET );
362                     if ( m_aURL.getBindingUrl( ) == ONEDRIVE_BASE_URL )
363                     {
364                         // reset the skip, so user gets a chance to cancel
365                         bSkipInitialPWAuth = false;
366                         libcmis::SessionFactory::setOAuth2AuthCodeProvider(AuthProvider::copyWebAuthCodeFallback);
367                         oauth2Data = boost::make_shared<libcmis::OAuth2Data>(
368                             ONEDRIVE_AUTH_URL, ONEDRIVE_TOKEN_URL,
369                             ONEDRIVE_SCOPE, ONEDRIVE_REDIRECT_URI,
370                             ONEDRIVE_CLIENT_ID, ONEDRIVE_CLIENT_SECRET );
371                     }
372                     try
373                     {
374                         m_pSession = libcmis::SessionFactory::createSession(
375                             OUSTR_TO_STDSTR( m_aURL.getBindingUrl( ) ),
376                             rUsername, rPassword, OUSTR_TO_STDSTR( m_aURL.getRepositoryId( ) ), false, oauth2Data );
377 
378                         if ( m_pSession == nullptr )
379                         {
380                             // Fail: session was not created
381                             ucbhelper::cancelCommandExecution(
382                                 ucb::IOErrorCode_INVALID_DEVICE,
383                                 generateErrorArguments(m_aURL),
384                                 xEnv);
385                         }
386                         else if ( m_pSession->getRepository() == nullptr )
387                         {
388                             // Fail: no repository or repository is invalid
389                             ucbhelper::cancelCommandExecution(
390                                 ucb::IOErrorCode_INVALID_DEVICE,
391                                 generateErrorArguments(m_aURL),
392                                 xEnv,
393                                 u"error accessing a repository"_ustr);
394                         }
395                         else
396                         {
397                             m_pProvider->registerSession(sSessionId, m_aURL.getUsername( ), m_pSession);
398                             if (m_aURL.getBindingUrl() == ONEDRIVE_BASE_URL
399                                 || m_aURL.getBindingUrl() == GDRIVE_BASE_URL)
400                             {
401                                 aAuthProvider.storeRefreshToken(rUsername, rPassword,
402                                                                 m_pSession->getRefreshToken());
403                             }
404                         }
405 
406                         bIsDone = true;
407                     }
408                     catch( const libcmis::Exception & e )
409                     {
410                         if (e.getType() == "dnsFailed")
411                         {
412                             uno::Any ex;
413                             ex <<= ucb::InteractiveNetworkResolveNameException(
414                                     OStringToOUString(e.what(), RTL_TEXTENCODING_UTF8),
415                                     getXWeak(),
416                                     task::InteractionClassification_ERROR,
417                                     m_aURL.getBindingUrl());
418                             ucbhelper::cancelCommandExecution(ex, xEnv);
419                         }
420                         else if (e.getType() == "connectFailed" || e.getType() == "connectTimeout")
421                         {
422                             uno::Any ex;
423                             ex <<= ucb::InteractiveNetworkConnectException(
424                                     OStringToOUString(e.what(), RTL_TEXTENCODING_UTF8),
425                                     getXWeak(),
426                                     task::InteractionClassification_ERROR,
427                                     m_aURL.getBindingUrl());
428                             ucbhelper::cancelCommandExecution(ex, xEnv);
429                         }
430                         else if (e.getType() == "transferFailed")
431                         {
432                             uno::Any ex;
433                             ex <<= ucb::InteractiveNetworkReadException(
434                                     OStringToOUString(e.what(), RTL_TEXTENCODING_UTF8),
435                                     getXWeak(),
436                                     task::InteractionClassification_ERROR,
437                                     m_aURL.getBindingUrl());
438                             ucbhelper::cancelCommandExecution(ex, xEnv);
439                         }
440                         else if (e.getType() != "permissionDenied")
441                         {
442                             SAL_INFO("ucb.ucp.cmis", "Unexpected libcmis exception: " << e.what());
443                             throw;
444                         }
445                     }
446                 }
447                 else
448                 {
449                     // Silently fail as the user cancelled the authentication
450                     ucbhelper::cancelCommandExecution(
451                                         ucb::IOErrorCode_ABORT,
452                                         uno::Sequence< uno::Any >( 0 ),
453                                         xEnv );
454                     throw uno::RuntimeException( );
455                 }
456             }
457         }
458         return m_pSession;
459     }
460 
getObjectType(const uno::Reference<ucb::XCommandEnvironment> & xEnv)461     libcmis::ObjectTypePtr const & Content::getObjectType( const uno::Reference< ucb::XCommandEnvironment >& xEnv )
462     {
463         if ( nullptr == m_pObjectType.get( ) && m_bTransient )
464         {
465             std::string typeId = m_bIsFolder ? "cmis:folder" : "cmis:document";
466             // The type to create needs to be fetched from the possible children types
467             // defined in the parent folder. Then, we'll pick up the first one we find matching
468             // cmis:folder or cmis:document (depending what we need to create).
469             // The easy case will work in most cases, but not on some servers (like Lotus Live)
470             libcmis::Folder* pParent = nullptr;
471             bool bTypeRestricted = false;
472             try
473             {
474                 pParent = dynamic_cast< libcmis::Folder* >( getObject( xEnv ).get( ) );
475             }
476             catch ( const libcmis::Exception& )
477             {
478             }
479 
480             if ( pParent )
481             {
482                 std::map< std::string, libcmis::PropertyPtr >& aProperties = pParent->getProperties( );
483                 std::map< std::string, libcmis::PropertyPtr >::iterator it = aProperties.find( "cmis:allowedChildObjectTypeIds" );
484                 if ( it != aProperties.end( ) )
485                 {
486                     libcmis::PropertyPtr pProperty = it->second;
487                     if ( pProperty )
488                     {
489                         std::vector< std::string > typesIds = pProperty->getStrings( );
490                         for ( const auto& rType : typesIds )
491                         {
492                             bTypeRestricted = true;
493                             libcmis::ObjectTypePtr type = getSession( xEnv )->getType( rType );
494 
495                             // FIXME Improve performances by adding getBaseTypeId( ) method to libcmis
496                             if ( type->getBaseType( )->getId( ) == typeId )
497                             {
498                                 m_pObjectType = type;
499                                 break;
500                             }
501                         }
502                     }
503                 }
504             }
505 
506             if ( !bTypeRestricted )
507                 m_pObjectType = getSession( xEnv )->getType( typeId );
508         }
509         return m_pObjectType;
510     }
511 
512 
getObject(const uno::Reference<ucb::XCommandEnvironment> & xEnv)513     libcmis::ObjectPtr const & Content::getObject( const uno::Reference< ucb::XCommandEnvironment >& xEnv )
514     {
515         // can't get the session for some reason
516         // the recent file opening at start up is an example.
517         try
518         {
519             if ( !getSession( xEnv ) )
520                 return m_pObject;
521         }
522         catch ( uno::RuntimeException& )
523         {
524             return m_pObject;
525         }
526         if ( !m_pObject.get() )
527         {
528             if ( !m_sObjectId.isEmpty( ) )
529             {
530                 try
531                 {
532                     m_pObject = getSession( xEnv )->getObject( OUSTR_TO_STDSTR( m_sObjectId ) );
533                 }
534                 catch ( const libcmis::Exception& )
535                 {
536                     SAL_INFO( "ucb.ucp.cmis", "object: " << OUSTR_TO_STDSTR(m_sObjectId));
537                     throw libcmis::Exception( "Object not found" );
538                 }
539             }
540             else if (!(m_sObjectPath.isEmpty() || m_sObjectPath == "/"))
541             {
542                 try
543                 {
544                     m_pObject = getSession( xEnv )->getObjectByPath( OUSTR_TO_STDSTR( m_sObjectPath ) );
545                 }
546                 catch ( const libcmis::Exception& )
547                 {
548                     // In some cases, getting the object from the path doesn't work,
549                     // but getting the parent from its path and the get the child in the list is OK.
550                     // It's weird, but needed to handle case where the path isn't the folders/files
551                     // names separated by '/' (as in Lotus Live)
552                     INetURLObject aParentUrl( m_sURL );
553                     std::string sName = OUSTR_TO_STDSTR( aParentUrl.getName( INetURLObject::LAST_SEGMENT, true, INetURLObject::DecodeMechanism::WithCharset ) );
554                     aParentUrl.removeSegment( );
555                     OUString sParentUrl = aParentUrl.GetMainURL( INetURLObject::DecodeMechanism::NONE );
556                     // Avoid infinite recursion if sParentUrl == m_sURL
557                     if (sParentUrl != m_sURL)
558                     {
559                         rtl::Reference<Content> xParent(new Content(m_xContext, m_pProvider, new ucbhelper::ContentIdentifier(sParentUrl)));
560                         libcmis::FolderPtr pParentFolder = boost::dynamic_pointer_cast< libcmis::Folder >(xParent->getObject(xEnv));
561                         if (pParentFolder)
562                         {
563                             std::vector< libcmis::ObjectPtr > children = pParentFolder->getChildren();
564                             auto it = std::find_if(children.begin(), children.end(),
565                                 [&sName](const libcmis::ObjectPtr& rChild) { return rChild->getName() == sName; });
566                             if (it != children.end())
567                                 m_pObject = *it;
568                         }
569                     }
570 
571                     if ( !m_pObject )
572                         throw libcmis::Exception( "Object not found" );
573                 }
574             }
575             else
576             {
577                 m_pObject = getSession( xEnv )->getRootFolder( );
578                 m_sObjectPath = "/";
579                 m_sObjectId = OUString( );
580             }
581         }
582 
583         return m_pObject;
584     }
585 
isFolder(const uno::Reference<ucb::XCommandEnvironment> & xEnv)586     bool Content::isFolder(const uno::Reference< ucb::XCommandEnvironment >& xEnv )
587     {
588         bool bIsFolder = false;
589         try
590         {
591             libcmis::ObjectPtr obj = getObject( xEnv );
592             if ( obj )
593                 bIsFolder = obj->getBaseType( ) == "cmis:folder";
594         }
595         catch ( const libcmis::Exception& e )
596         {
597             SAL_INFO( "ucb.ucp.cmis", "Unexpected libcmis exception: " << e.what( ) );
598 
599             ucbhelper::cancelCommandExecution(
600                             ucb::IOErrorCode_GENERAL,
601                             uno::Sequence< uno::Any >( 0 ),
602                             xEnv,
603                             OUString::createFromAscii( e.what( ) ) );
604 
605         }
606         return bIsFolder;
607     }
608 
getBadArgExcept()609     uno::Any Content::getBadArgExcept()
610     {
611         return uno::Any( lang::IllegalArgumentException(
612             u"Wrong argument type!"_ustr,
613             getXWeak(), -1) );
614     }
615 
updateProperties(const uno::Any & iCmisProps,const uno::Reference<ucb::XCommandEnvironment> & xEnv)616     libcmis::ObjectPtr Content::updateProperties(
617          const uno::Any& iCmisProps,
618          const uno::Reference< ucb::XCommandEnvironment >& xEnv )
619     {
620         // Convert iCmisProps to Cmis Properties;
621         uno::Sequence< document::CmisProperty > aPropsSeq;
622         iCmisProps >>= aPropsSeq;
623         std::map< std::string, libcmis::PropertyPtr > aProperties;
624 
625         for (const auto& rProp : aPropsSeq)
626         {
627             std::string id = OUSTR_TO_STDSTR( rProp.Id );
628             libcmis::PropertyPtr prop = lcl_unoToCmisProperty( rProp );
629             aProperties.insert( std::pair<std::string, libcmis::PropertyPtr>( id, prop ) );
630         }
631         libcmis::ObjectPtr updateObj;
632         try
633         {
634             updateObj = getObject( xEnv )->updateProperties( aProperties );
635         }
636         catch ( const libcmis::Exception& e )
637         {
638             SAL_INFO( "ucb.ucp.cmis", "Unexpected libcmis exception: "<< e.what( ) );
639         }
640 
641         return updateObj;
642     }
643 
getPropertyValues(const uno::Sequence<beans::Property> & rProperties,const uno::Reference<ucb::XCommandEnvironment> & xEnv)644     uno::Reference< sdbc::XRow > Content::getPropertyValues(
645             const uno::Sequence< beans::Property >& rProperties,
646             const uno::Reference< ucb::XCommandEnvironment >& xEnv )
647     {
648         rtl::Reference< ::ucbhelper::PropertyValueSet > xRow = new ::ucbhelper::PropertyValueSet( m_xContext );
649 
650         for( const beans::Property& rProp : rProperties )
651         {
652             try
653             {
654                 if ( rProp.Name == "IsDocument" )
655                 {
656                     try
657                     {
658                         libcmis::ObjectPtr obj = getObject( xEnv );
659                         if ( obj )
660                             xRow->appendBoolean( rProp, obj->getBaseType( ) == "cmis:document" );
661                     }
662                     catch ( const libcmis::Exception& )
663                     {
664                         if ( m_pObjectType.get( ) )
665                             xRow->appendBoolean( rProp, getObjectType( xEnv )->getBaseType()->getId( ) == "cmis:document" );
666                         else
667                             xRow->appendVoid( rProp );
668                     }
669                 }
670                 else if ( rProp.Name == "IsFolder" )
671                 {
672                     try
673                     {
674                         libcmis::ObjectPtr obj = getObject( xEnv );
675                         if ( obj )
676                             xRow->appendBoolean( rProp, obj->getBaseType( ) == "cmis:folder" );
677                         else
678                             xRow->appendBoolean( rProp, false );
679                     }
680                     catch ( const libcmis::Exception& )
681                     {
682                         if ( m_pObjectType.get( ) )
683                             xRow->appendBoolean( rProp, getObjectType( xEnv )->getBaseType()->getId( ) == "cmis:folder" );
684                         else
685                             xRow->appendVoid( rProp );
686                     }
687                 }
688                 else if ( rProp.Name == "Title" )
689                 {
690                     OUString sTitle;
691                     try
692                     {
693                         sTitle = STD_TO_OUSTR( getObject( xEnv )->getName() );
694                     }
695                     catch ( const libcmis::Exception& )
696                     {
697                         if ( !m_pObjectProps.empty() )
698                         {
699                             std::map< std::string, libcmis::PropertyPtr >::iterator it = m_pObjectProps.find( "cmis:name" );
700                             if ( it != m_pObjectProps.end( ) )
701                             {
702                                 std::vector< std::string > values = it->second->getStrings( );
703                                 if ( !values.empty() )
704                                     sTitle = STD_TO_OUSTR( values.front( ) );
705                             }
706                         }
707                     }
708 
709                     // Nothing worked... get it from the path
710                     if ( sTitle.isEmpty( ) )
711                     {
712                         OUString sPath = m_sObjectPath;
713 
714                         // Get rid of the trailing slash problem
715                         if ( sPath.endsWith("/") )
716                             sPath = sPath.copy( 0, sPath.getLength() - 1 );
717 
718                         // Get the last segment
719                         sal_Int32 nPos = sPath.lastIndexOf( '/' );
720                         if ( nPos >= 0 )
721                             sTitle = sPath.copy( nPos + 1 );
722                     }
723 
724                     if ( !sTitle.isEmpty( ) )
725                         xRow->appendString( rProp, sTitle );
726                     else
727                         xRow->appendVoid( rProp );
728                 }
729                 else if ( rProp.Name == "ObjectId" )
730                 {
731                     OUString sId;
732                     try
733                     {
734                         sId = STD_TO_OUSTR( getObject( xEnv )->getId() );
735                     }
736                     catch ( const libcmis::Exception& )
737                     {
738                         if ( !m_pObjectProps.empty() )
739                         {
740                             std::map< std::string, libcmis::PropertyPtr >::iterator it = m_pObjectProps.find( "cmis:objectId" );
741                             if ( it != m_pObjectProps.end( ) )
742                             {
743                                 std::vector< std::string > values = it->second->getStrings( );
744                                 if ( !values.empty() )
745                                     sId = STD_TO_OUSTR( values.front( ) );
746                             }
747                         }
748                     }
749 
750                     if ( !sId.isEmpty( ) )
751                         xRow->appendString( rProp, sId );
752                     else
753                         xRow->appendVoid( rProp );
754                 }
755                 else if ( rProp.Name == "TitleOnServer" )
756                 {
757                     xRow->appendString( rProp, m_sObjectPath);
758                 }
759                 else if ( rProp.Name == "IsReadOnly" )
760                 {
761                     boost::shared_ptr< libcmis::AllowableActions > allowableActions = getObject( xEnv )->getAllowableActions( );
762                     bool bReadOnly = false;
763                     if ( !allowableActions->isAllowed( libcmis::ObjectAction::SetContentStream ) &&
764                          !allowableActions->isAllowed( libcmis::ObjectAction::CheckIn ) )
765                         bReadOnly = true;
766 
767                     xRow->appendBoolean( rProp, bReadOnly );
768                 }
769                 else if ( rProp.Name == "DateCreated" )
770                 {
771                     util::DateTime aTime = lcl_boostToUnoTime( getObject( xEnv )->getCreationDate( ) );
772                     xRow->appendTimestamp( rProp, aTime );
773                 }
774                 else if ( rProp.Name == "DateModified" )
775                 {
776                     util::DateTime aTime = lcl_boostToUnoTime( getObject( xEnv )->getLastModificationDate( ) );
777                     xRow->appendTimestamp( rProp, aTime );
778                 }
779                 else if ( rProp.Name == "Size" )
780                 {
781                     try
782                     {
783                         libcmis::Document* document = dynamic_cast< libcmis::Document* >( getObject( xEnv ).get( ) );
784                         if ( nullptr != document )
785                             xRow->appendLong( rProp, document->getContentLength() );
786                         else
787                             xRow->appendVoid( rProp );
788                     }
789                     catch ( const libcmis::Exception& )
790                     {
791                         xRow->appendVoid( rProp );
792                     }
793                 }
794                 else if ( rProp.Name == "CreatableContentsInfo" )
795                 {
796                     xRow->appendObject( rProp, uno::Any( queryCreatableContentsInfo( xEnv ) ) );
797                 }
798                 else if ( rProp.Name == "MediaType" )
799                 {
800                     try
801                     {
802                         libcmis::Document* document = dynamic_cast< libcmis::Document* >( getObject( xEnv ).get( ) );
803                         if ( nullptr != document )
804                             xRow->appendString( rProp, STD_TO_OUSTR( document->getContentType() ) );
805                         else
806                             xRow->appendVoid( rProp );
807                     }
808                     catch ( const libcmis::Exception& )
809                     {
810                         xRow->appendVoid( rProp );
811                     }
812                 }
813                 else if ( rProp.Name == "IsVolume" )
814                 {
815                     xRow->appendBoolean( rProp, false );
816                 }
817                 else if ( rProp.Name == "IsRemote" )
818                 {
819                     xRow->appendBoolean( rProp, false );
820                 }
821                 else if ( rProp.Name == "IsRemoveable" )
822                 {
823                     xRow->appendBoolean( rProp, false );
824                 }
825                 else if ( rProp.Name == "IsFloppy" )
826                 {
827                     xRow->appendBoolean( rProp, false );
828                 }
829                 else if ( rProp.Name == "IsCompactDisc" )
830                 {
831                     xRow->appendBoolean( rProp, false );
832                 }
833                 else if ( rProp.Name == "IsHidden" )
834                 {
835                     xRow->appendBoolean( rProp, false );
836                 }
837                 else if ( rProp.Name == "TargetURL" )
838                 {
839                     xRow->appendString( rProp, u""_ustr );
840                 }
841                 else if ( rProp.Name == "BaseURI" )
842                 {
843                     xRow->appendString( rProp, m_aURL.getBindingUrl( ) );
844                 }
845                 else if ( rProp.Name == "CmisProperties" )
846                 {
847                     try
848                     {
849                         libcmis::ObjectPtr object = getObject( xEnv );
850                         std::map< std::string, libcmis::PropertyPtr >& aProperties = object->getProperties( );
851                         uno::Sequence< document::CmisProperty > aCmisProperties( aProperties.size( ) );
852                         document::CmisProperty* pCmisProps = aCmisProperties.getArray( );
853                         sal_Int32 i = 0;
854                         for ( const auto& [sId, rProperty] : aProperties )
855                         {
856                             auto sDisplayName = rProperty->getPropertyType()->getDisplayName( );
857                             bool bUpdatable = rProperty->getPropertyType()->isUpdatable( );
858                             bool bRequired = rProperty->getPropertyType()->isRequired( );
859                             bool bMultiValued = rProperty->getPropertyType()->isMultiValued();
860                             bool bOpenChoice = rProperty->getPropertyType()->isOpenChoice();
861 
862                             pCmisProps[i].Id = STD_TO_OUSTR( sId );
863                             pCmisProps[i].Name = STD_TO_OUSTR( sDisplayName );
864                             pCmisProps[i].Updatable = bUpdatable;
865                             pCmisProps[i].Required = bRequired;
866                             pCmisProps[i].MultiValued = bMultiValued;
867                             pCmisProps[i].OpenChoice = bOpenChoice;
868                             pCmisProps[i].Value = lcl_cmisPropertyToUno( rProperty );
869                             switch ( rProperty->getPropertyType( )->getType( ) )
870                             {
871                                 default:
872                                 case libcmis::PropertyType::String:
873                                     pCmisProps[i].Type = CMIS_TYPE_STRING;
874                                 break;
875                                 case libcmis::PropertyType::Integer:
876                                     pCmisProps[i].Type = CMIS_TYPE_INTEGER;
877                                 break;
878                                 case libcmis::PropertyType::Decimal:
879                                     pCmisProps[i].Type = CMIS_TYPE_DECIMAL;
880                                 break;
881                                 case libcmis::PropertyType::Bool:
882                                     pCmisProps[i].Type = CMIS_TYPE_BOOL;
883                                 break;
884                                 case libcmis::PropertyType::DateTime:
885                                     pCmisProps[i].Type = CMIS_TYPE_DATETIME;
886                                 break;
887                             }
888                             ++i;
889                         }
890                         xRow->appendObject( rProp.Name, uno::Any( aCmisProperties ) );
891                     }
892                     catch ( const libcmis::Exception& )
893                     {
894                         xRow->appendVoid( rProp );
895                     }
896                 }
897                 else if ( rProp.Name == "IsVersionable" )
898                 {
899                     try
900                     {
901                         libcmis::ObjectPtr object = getObject( xEnv );
902                         bool bIsVersionable = object->getTypeDescription( )->isVersionable( );
903                         xRow->appendBoolean( rProp, bIsVersionable );
904                     }
905                     catch ( const libcmis::Exception& )
906                     {
907                         xRow->appendVoid( rProp );
908                     }
909                 }
910                 else if ( rProp.Name == "CanCheckOut" )
911                 {
912                     try
913                     {
914                         libcmis::ObjectPtr pObject = getObject( xEnv );
915                         libcmis::AllowableActionsPtr aAllowables = pObject->getAllowableActions( );
916                         bool bAllowed = false;
917                         if ( aAllowables )
918                         {
919                             bAllowed = aAllowables->isAllowed( libcmis::ObjectAction::CheckOut );
920                         }
921                         xRow->appendBoolean( rProp, bAllowed );
922                     }
923                     catch ( const libcmis::Exception& )
924                     {
925                         xRow->appendVoid( rProp );
926                     }
927                 }
928                 else if ( rProp.Name == "CanCancelCheckOut" )
929                 {
930                     try
931                     {
932                         libcmis::ObjectPtr pObject = getObject( xEnv );
933                         libcmis::AllowableActionsPtr aAllowables = pObject->getAllowableActions( );
934                         bool bAllowed = false;
935                         if ( aAllowables )
936                         {
937                             bAllowed = aAllowables->isAllowed( libcmis::ObjectAction::CancelCheckOut );
938                         }
939                         xRow->appendBoolean( rProp, bAllowed );
940                     }
941                     catch ( const libcmis::Exception& )
942                     {
943                         xRow->appendVoid( rProp );
944                     }
945                 }
946                 else if ( rProp.Name == "CanCheckIn" )
947                 {
948                     try
949                     {
950                         libcmis::ObjectPtr pObject = getObject( xEnv );
951                         libcmis::AllowableActionsPtr aAllowables = pObject->getAllowableActions( );
952                         bool bAllowed = false;
953                         if ( aAllowables )
954                         {
955                             bAllowed = aAllowables->isAllowed( libcmis::ObjectAction::CheckIn );
956                         }
957                         xRow->appendBoolean( rProp, bAllowed );
958                     }
959                     catch ( const libcmis::Exception& )
960                     {
961                         xRow->appendVoid( rProp );
962                     }
963                 }
964                 else
965                     SAL_INFO( "ucb.ucp.cmis", "Looking for unsupported property " << rProp.Name );
966             }
967             catch (const libcmis::Exception&)
968             {
969                 xRow->appendVoid( rProp );
970             }
971         }
972 
973         return xRow;
974     }
975 
open(const ucb::OpenCommandArgument2 & rOpenCommand,const uno::Reference<ucb::XCommandEnvironment> & xEnv)976     uno::Any Content::open(const ucb::OpenCommandArgument2 & rOpenCommand,
977         const uno::Reference< ucb::XCommandEnvironment > & xEnv )
978     {
979         bool bIsFolder = isFolder( xEnv );
980 
981         // Handle the case of the non-existing file
982         if ( !getObject( xEnv ) )
983         {
984             uno::Sequence< uno::Any > aArgs{ uno::Any(m_xIdentifier->getContentIdentifier()) };
985             uno::Any aErr(
986                 ucb::InteractiveAugmentedIOException(OUString(), getXWeak(),
987                     task::InteractionClassification_ERROR,
988                     bIsFolder ? ucb::IOErrorCode_NOT_EXISTING_PATH : ucb::IOErrorCode_NOT_EXISTING, aArgs)
989             );
990 
991             ucbhelper::cancelCommandExecution(aErr, xEnv);
992         }
993 
994         uno::Any aRet;
995 
996         bool bOpenFolder = (
997             ( rOpenCommand.Mode == ucb::OpenMode::ALL ) ||
998             ( rOpenCommand.Mode == ucb::OpenMode::FOLDERS ) ||
999             ( rOpenCommand.Mode == ucb::OpenMode::DOCUMENTS )
1000          );
1001 
1002         if ( bOpenFolder && bIsFolder )
1003         {
1004             uno::Reference< ucb::XDynamicResultSet > xSet
1005                 = new DynamicResultSet(m_xContext, this, rOpenCommand, xEnv );
1006             aRet <<= xSet;
1007         }
1008         else if ( rOpenCommand.Sink.is() )
1009         {
1010             if (
1011                 ( rOpenCommand.Mode == ucb::OpenMode::DOCUMENT_SHARE_DENY_NONE ) ||
1012                 ( rOpenCommand.Mode == ucb::OpenMode::DOCUMENT_SHARE_DENY_WRITE )
1013                )
1014             {
1015                 ucbhelper::cancelCommandExecution(
1016                     uno::Any ( ucb::UnsupportedOpenModeException
1017                         ( OUString(), getXWeak(),
1018                           sal_Int16( rOpenCommand.Mode ) ) ),
1019                         xEnv );
1020             }
1021 
1022             if ( !feedSink( rOpenCommand.Sink, xEnv ) )
1023             {
1024                 // Note: rOpenCommand.Sink may contain an XStream
1025                 //       implementation. Support for this type of
1026                 //       sink is optional...
1027                 SAL_INFO( "ucb.ucp.cmis", "Failed to copy data to sink" );
1028 
1029                 ucbhelper::cancelCommandExecution(
1030                     uno::Any (ucb::UnsupportedDataSinkException
1031                         ( OUString(), getXWeak(),
1032                           rOpenCommand.Sink ) ),
1033                         xEnv );
1034             }
1035         }
1036         else
1037             SAL_INFO( "ucb.ucp.cmis", "Open falling through ..." );
1038 
1039         return aRet;
1040     }
1041 
checkIn(const ucb::CheckinArgument & rArg,const uno::Reference<ucb::XCommandEnvironment> & xEnv)1042     OUString Content::checkIn( const ucb::CheckinArgument& rArg,
1043         const uno::Reference< ucb::XCommandEnvironment > & xEnv )
1044     {
1045         ucbhelper::Content aSourceContent( rArg.SourceURL, xEnv, comphelper::getProcessComponentContext( ) );
1046         uno::Reference< io::XInputStream > xIn = aSourceContent.openStream( );
1047 
1048         libcmis::ObjectPtr object;
1049         try
1050         {
1051             object = getObject( xEnv );
1052         }
1053         catch ( const libcmis::Exception& e )
1054         {
1055             SAL_INFO( "ucb.ucp.cmis", "Unexpected libcmis exception: " << e.what( ) );
1056             ucbhelper::cancelCommandExecution(
1057                                 ucb::IOErrorCode_GENERAL,
1058                                 uno::Sequence< uno::Any >( 0 ),
1059                                 xEnv,
1060                                 OUString::createFromAscii( e.what() ) );
1061         }
1062 
1063         libcmis::Document* pPwc = dynamic_cast< libcmis::Document* >( object.get( ) );
1064         if ( !pPwc )
1065         {
1066             ucbhelper::cancelCommandExecution(
1067                                 ucb::IOErrorCode_GENERAL,
1068                                 uno::Sequence< uno::Any >( 0 ),
1069                                 xEnv,
1070                                 u"Checkin only supported by documents"_ustr );
1071         }
1072 
1073         boost::shared_ptr< std::ostream > pOut( new std::ostringstream ( std::ios_base::binary | std::ios_base::in | std::ios_base::out ) );
1074         uno::Reference < io::XOutputStream > xOutput = new StdOutputStream( pOut );
1075         copyData( xIn, xOutput );
1076 
1077         std::map< std::string, libcmis::PropertyPtr > newProperties;
1078         libcmis::DocumentPtr pDoc;
1079 
1080         try
1081         {
1082             pDoc = pPwc->checkIn( rArg.MajorVersion, OUSTR_TO_STDSTR( rArg.VersionComment ), newProperties,
1083                                   pOut, OUSTR_TO_STDSTR( rArg.MimeType ), OUSTR_TO_STDSTR( rArg.NewTitle ) );
1084         }
1085         catch ( const libcmis::Exception& e )
1086         {
1087             SAL_INFO( "ucb.ucp.cmis", "Unexpected libcmis exception: " << e.what( ) );
1088             ucbhelper::cancelCommandExecution(
1089                                 ucb::IOErrorCode_GENERAL,
1090                                 uno::Sequence< uno::Any >( 0 ),
1091                                 xEnv,
1092                                 OUString::createFromAscii( e.what() ) );
1093         }
1094 
1095         // Get the URL and send it back as a result
1096         URL aCmisUrl( m_sURL );
1097         std::vector< std::string > aPaths = pDoc->getPaths( );
1098         if ( !aPaths.empty() )
1099         {
1100             aCmisUrl.setObjectPath(STD_TO_OUSTR(aPaths.front()));
1101         }
1102         else
1103         {
1104             // We may have unfiled document depending on the server, those
1105             // won't have any path, use their ID instead
1106             aCmisUrl.setObjectId(STD_TO_OUSTR(pDoc->getId()));
1107         }
1108         return aCmisUrl.asString( );
1109     }
1110 
checkOut(const uno::Reference<ucb::XCommandEnvironment> & xEnv)1111     OUString Content::checkOut( const uno::Reference< ucb::XCommandEnvironment > & xEnv )
1112     {
1113         OUString aRet;
1114         try
1115         {
1116             // Checkout the document if possible
1117             libcmis::DocumentPtr pDoc = boost::dynamic_pointer_cast< libcmis::Document >( getObject( xEnv ) );
1118             if ( pDoc.get( ) == nullptr )
1119             {
1120                 ucbhelper::cancelCommandExecution(
1121                                     ucb::IOErrorCode_GENERAL,
1122                                     uno::Sequence< uno::Any >( 0 ),
1123                                     xEnv,
1124                                     u"Checkout only supported by documents"_ustr );
1125             }
1126             libcmis::DocumentPtr pPwc = pDoc->checkOut( );
1127 
1128             // Compute the URL of the Private Working Copy (PWC)
1129             URL aCmisUrl( m_sURL );
1130             std::vector< std::string > aPaths = pPwc->getPaths( );
1131             if ( !aPaths.empty() )
1132             {
1133                 aCmisUrl.setObjectPath(STD_TO_OUSTR(aPaths.front()));
1134             }
1135             else
1136             {
1137                 // We may have unfiled PWC depending on the server, those
1138                 // won't have any path, use their ID instead
1139                 auto sId = pPwc->getId( );
1140                 aCmisUrl.setObjectId( STD_TO_OUSTR( sId ) );
1141             }
1142             aRet = aCmisUrl.asString( );
1143         }
1144         catch ( const libcmis::Exception& e )
1145         {
1146             SAL_INFO( "ucb.ucp.cmis", "Unexpected libcmis exception: " << e.what( ) );
1147             ucbhelper::cancelCommandExecution(
1148                                 ucb::IOErrorCode_GENERAL,
1149                                 uno::Sequence< uno::Any >( 0 ),
1150                                 xEnv,
1151                                 o3tl::runtimeToOUString(e.what()));
1152         }
1153         return aRet;
1154     }
1155 
cancelCheckOut(const uno::Reference<ucb::XCommandEnvironment> & xEnv)1156     OUString Content::cancelCheckOut( const uno::Reference< ucb::XCommandEnvironment > & xEnv )
1157     {
1158         OUString aRet;
1159         try
1160         {
1161             libcmis::DocumentPtr pPwc = boost::dynamic_pointer_cast< libcmis::Document >( getObject( xEnv ) );
1162             if ( pPwc.get( ) == nullptr )
1163             {
1164                 ucbhelper::cancelCommandExecution(
1165                                     ucb::IOErrorCode_GENERAL,
1166                                     uno::Sequence< uno::Any >( 0 ),
1167                                     xEnv,
1168                                     u"CancelCheckout only supported by documents"_ustr );
1169             }
1170             pPwc->cancelCheckout( );
1171 
1172             // Get the Original document (latest version)
1173             std::vector< libcmis::DocumentPtr > aVersions = pPwc->getAllVersions( );
1174             for ( const auto& rVersion : aVersions )
1175             {
1176                 libcmis::DocumentPtr pVersion = rVersion;
1177                 std::map< std::string, libcmis::PropertyPtr > aProps = pVersion->getProperties( );
1178                 bool bIsLatestVersion = false;
1179                 std::map< std::string, libcmis::PropertyPtr >::iterator propIt = aProps.find( std::string( "cmis:isLatestVersion" ) );
1180                 if ( propIt != aProps.end( ) && !propIt->second->getBools( ).empty( ) )
1181                 {
1182                     bIsLatestVersion = propIt->second->getBools( ).front( );
1183                 }
1184 
1185                 if ( bIsLatestVersion )
1186                 {
1187                     // Compute the URL of the Document
1188                     URL aCmisUrl( m_sURL );
1189                     std::vector< std::string > aPaths = pVersion->getPaths( );
1190                     if ( !aPaths.empty() )
1191                     {
1192                         auto sPath = aPaths.front( );
1193                         aCmisUrl.setObjectPath( STD_TO_OUSTR( sPath ) );
1194                     }
1195                     else
1196                     {
1197                         // We may have unfiled doc depending on the server, those
1198                         // won't have any path, use their ID instead
1199                         auto sId = pVersion->getId( );
1200                         aCmisUrl.setObjectId( STD_TO_OUSTR( sId ) );
1201                     }
1202                     aRet = aCmisUrl.asString( );
1203                     break;
1204                 }
1205             }
1206         }
1207         catch ( const libcmis::Exception& e )
1208         {
1209             SAL_INFO( "ucb.ucp.cmis", "Unexpected libcmis exception: " << e.what( ) );
1210             ucbhelper::cancelCommandExecution(
1211                                 ucb::IOErrorCode_GENERAL,
1212                                 uno::Sequence< uno::Any >( 0 ),
1213                                 xEnv,
1214                                 o3tl::runtimeToOUString(e.what()));
1215         }
1216         return aRet;
1217     }
1218 
getAllVersions(const uno::Reference<ucb::XCommandEnvironment> & xEnv)1219     uno::Sequence< document::CmisVersion> Content::getAllVersions( const uno::Reference< ucb::XCommandEnvironment > & xEnv )
1220     {
1221         try
1222         {
1223             // get the document
1224             libcmis::DocumentPtr pDoc = boost::dynamic_pointer_cast< libcmis::Document >( getObject( xEnv ) );
1225             if ( pDoc.get( ) == nullptr )
1226             {
1227                 ucbhelper::cancelCommandExecution(
1228                                     ucb::IOErrorCode_GENERAL,
1229                                     uno::Sequence< uno::Any >( 0 ),
1230                                     xEnv,
1231                                     u"Can not get the document"_ustr );
1232             }
1233             std::vector< libcmis::DocumentPtr > aCmisVersions = pDoc->getAllVersions( );
1234             uno::Sequence< document::CmisVersion > aVersions( aCmisVersions.size( ) );
1235             auto aVersionsRange = asNonConstRange(aVersions);
1236             int i = 0;
1237             for ( const auto& rVersion : aCmisVersions )
1238             {
1239                 libcmis::DocumentPtr pVersion = rVersion;
1240                 aVersionsRange[i].Id = STD_TO_OUSTR( pVersion->getId( ) );
1241                 aVersionsRange[i].Author = STD_TO_OUSTR( pVersion->getCreatedBy( ) );
1242                 aVersionsRange[i].TimeStamp = lcl_boostToUnoTime( pVersion->getLastModificationDate( ) );
1243                 aVersionsRange[i].Comment = STD_TO_OUSTR( pVersion->getStringProperty("cmis:checkinComment") );
1244                 ++i;
1245             }
1246             return aVersions;
1247         }
1248         catch ( const libcmis::Exception& e )
1249         {
1250             SAL_INFO( "ucb.ucp.cmis", "Unexpected libcmis exception: " << e.what( ) );
1251             ucbhelper::cancelCommandExecution(
1252                     ucb::IOErrorCode_GENERAL,
1253                     uno::Sequence< uno::Any >( 0 ),
1254                     xEnv,
1255                     o3tl::runtimeToOUString(e.what()));
1256         }
1257         return uno::Sequence< document::CmisVersion > ( );
1258     }
1259 
transfer(const ucb::TransferInfo & rTransferInfo,const uno::Reference<ucb::XCommandEnvironment> & xEnv)1260     void Content::transfer( const ucb::TransferInfo& rTransferInfo,
1261         const uno::Reference< ucb::XCommandEnvironment > & xEnv )
1262     {
1263         // If the source isn't on the same CMIS repository, then simply copy
1264         INetURLObject aSourceUrl( rTransferInfo.SourceURL );
1265         if ( aSourceUrl.GetProtocol() != INetProtocol::Cmis )
1266         {
1267             OUString sSrcBindingUrl = URL( rTransferInfo.SourceURL ).getBindingUrl( );
1268             if ( sSrcBindingUrl != m_aURL.getBindingUrl( ) )
1269             {
1270                 ucbhelper::cancelCommandExecution(
1271                     uno::Any(
1272                         ucb::InteractiveBadTransferURLException(
1273                             u"Unsupported URL scheme!"_ustr,
1274                             getXWeak() ) ),
1275                     xEnv );
1276             }
1277         }
1278 
1279         SAL_INFO( "ucb.ucp.cmis", "TODO - Content::transfer()" );
1280     }
1281 
insert(const uno::Reference<io::XInputStream> & xInputStream,bool bReplaceExisting,std::u16string_view rMimeType,const uno::Reference<ucb::XCommandEnvironment> & xEnv)1282     void Content::insert( const uno::Reference< io::XInputStream > & xInputStream,
1283         bool bReplaceExisting, std::u16string_view rMimeType,
1284         const uno::Reference< ucb::XCommandEnvironment >& xEnv )
1285     {
1286         if ( !xInputStream.is() )
1287         {
1288             ucbhelper::cancelCommandExecution( uno::Any
1289                 ( ucb::MissingInputStreamException
1290                   ( OUString(), getXWeak() ) ),
1291                 xEnv );
1292         }
1293 
1294         // For transient content, the URL is the one of the parent
1295         if ( !m_bTransient )
1296             return;
1297 
1298         OUString sNewPath;
1299 
1300         // Try to get the object from the server if there is any
1301         libcmis::FolderPtr pFolder;
1302         try
1303         {
1304             pFolder = boost::dynamic_pointer_cast< libcmis::Folder >( getObject( xEnv ) );
1305         }
1306         catch ( const libcmis::Exception& )
1307         {
1308         }
1309 
1310         if ( pFolder == nullptr )
1311             return;
1312 
1313         libcmis::ObjectPtr object;
1314         std::map< std::string, libcmis::PropertyPtr >::iterator it = m_pObjectProps.find( "cmis:name" );
1315         if ( it == m_pObjectProps.end( ) )
1316         {
1317             ucbhelper::cancelCommandExecution( uno::Any
1318                 ( uno::RuntimeException( u"Missing name property"_ustr,
1319                     getXWeak() ) ),
1320                 xEnv );
1321         }
1322         auto newName = it->second->getStrings( ).front( );
1323         auto newPath = OUSTR_TO_STDSTR( m_sObjectPath );
1324         if ( !newPath.empty( ) && newPath[ newPath.size( ) - 1 ] != '/' )
1325             newPath += "/";
1326         newPath += newName;
1327         try
1328         {
1329             if ( !m_sObjectId.isEmpty( ) )
1330                 object = getSession( xEnv )->getObject( OUSTR_TO_STDSTR( m_sObjectId) );
1331             else
1332                 object = getSession( xEnv )->getObjectByPath( newPath );
1333             sNewPath = STD_TO_OUSTR( newPath );
1334         }
1335         catch ( const libcmis::Exception& )
1336         {
1337             // Nothing matched the path
1338         }
1339 
1340         if ( nullptr != object.get( ) )
1341         {
1342             // Are the base type matching?
1343             if ( object->getBaseType( ) != m_pObjectType->getBaseType( )->getId() )
1344             {
1345                 ucbhelper::cancelCommandExecution( uno::Any
1346                     ( uno::RuntimeException( u"Can't change a folder into a document and vice-versa."_ustr,
1347                         getXWeak() ) ),
1348                     xEnv );
1349             }
1350 
1351             // Update the existing object if it's a document
1352             libcmis::Document* document = dynamic_cast< libcmis::Document* >( object.get( ) );
1353             if ( nullptr != document )
1354             {
1355                 boost::shared_ptr< std::ostream > pOut( new std::ostringstream ( std::ios_base::binary | std::ios_base::in | std::ios_base::out ) );
1356                 uno::Reference < io::XOutputStream > xOutput = new StdOutputStream( pOut );
1357                 copyData( xInputStream, xOutput );
1358                 try
1359                 {
1360                     document->setContentStream( pOut, OUSTR_TO_STDSTR( rMimeType ), std::string( ), bReplaceExisting );
1361                 }
1362                 catch ( const libcmis::Exception& )
1363                 {
1364                     ucbhelper::cancelCommandExecution( uno::Any
1365                         ( uno::RuntimeException( u"Error when setting document content"_ustr,
1366                             getXWeak() ) ),
1367                         xEnv );
1368                 }
1369             }
1370         }
1371         else
1372         {
1373             // We need to create a brand new object... either folder or document
1374             bool bIsFolder = getObjectType( xEnv )->getBaseType( )->getId( ) == "cmis:folder";
1375             setCmisProperty( "cmis:objectTypeId", getObjectType( xEnv )->getId( ), xEnv );
1376 
1377             if ( bIsFolder )
1378             {
1379                 try
1380                 {
1381                     pFolder->createFolder( m_pObjectProps );
1382                     sNewPath = STD_TO_OUSTR( newPath );
1383                 }
1384                 catch ( const libcmis::Exception& )
1385                 {
1386                     ucbhelper::cancelCommandExecution( uno::Any
1387                         ( uno::RuntimeException( u"Error when creating folder"_ustr,
1388                             getXWeak() ) ),
1389                         xEnv );
1390                 }
1391             }
1392             else
1393             {
1394                 boost::shared_ptr< std::ostream > pOut( new std::ostringstream ( std::ios_base::binary | std::ios_base::in | std::ios_base::out ) );
1395                 uno::Reference < io::XOutputStream > xOutput = new StdOutputStream( pOut );
1396                 copyData( xInputStream, xOutput );
1397                 try
1398                 {
1399                     pFolder->createDocument( m_pObjectProps, pOut, OUSTR_TO_STDSTR( rMimeType ), std::string() );
1400                     sNewPath = STD_TO_OUSTR( newPath );
1401                 }
1402                 catch ( const libcmis::Exception& )
1403                 {
1404                     ucbhelper::cancelCommandExecution( uno::Any
1405                         ( uno::RuntimeException( u"Error when creating document"_ustr,
1406                             getXWeak() ) ),
1407                         xEnv );
1408                 }
1409             }
1410         }
1411 
1412         if ( sNewPath.isEmpty( ) && m_sObjectId.isEmpty( ) )
1413             return;
1414 
1415         // Update the current content: it's no longer transient
1416         m_sObjectPath = sNewPath;
1417         URL aUrl( m_sURL );
1418         aUrl.setObjectPath( m_sObjectPath );
1419         aUrl.setObjectId( m_sObjectId );
1420         m_sURL = aUrl.asString( );
1421         m_pObject.reset( );
1422         m_pObjectType.reset( );
1423         m_pObjectProps.clear( );
1424         m_bTransient = false;
1425         inserted();
1426     }
1427 
1428     const int TRANSFER_BUFFER_SIZE = 65536;
1429 
copyData(const uno::Reference<io::XInputStream> & xIn,const uno::Reference<io::XOutputStream> & xOut)1430     void Content::copyData(
1431         const uno::Reference< io::XInputStream >& xIn,
1432         const uno::Reference< io::XOutputStream >& xOut )
1433     {
1434         uno::Sequence< sal_Int8 > theData( TRANSFER_BUFFER_SIZE );
1435 
1436         while ( xIn->readBytes( theData, TRANSFER_BUFFER_SIZE ) > 0 )
1437             xOut->writeBytes( theData );
1438 
1439         xOut->closeOutput();
1440     }
1441 
setPropertyValues(const uno::Sequence<beans::PropertyValue> & rValues,const uno::Reference<ucb::XCommandEnvironment> & xEnv)1442     uno::Sequence< uno::Any > Content::setPropertyValues(
1443             const uno::Sequence< beans::PropertyValue >& rValues,
1444             const uno::Reference< ucb::XCommandEnvironment >& xEnv )
1445     {
1446         try
1447         {
1448             // Get the already set properties if possible
1449             if ( !m_bTransient && getObject( xEnv ).get( ) )
1450             {
1451                 m_pObjectProps.clear( );
1452                 m_pObjectType = getObject( xEnv )->getTypeDescription();
1453             }
1454         }
1455         catch ( const libcmis::Exception& e )
1456         {
1457             SAL_INFO( "ucb.ucp.cmis", "Unexpected libcmis exception: " << e.what( ) );
1458             ucbhelper::cancelCommandExecution(
1459                                 ucb::IOErrorCode_GENERAL,
1460                                 uno::Sequence< uno::Any >( 0 ),
1461                                 xEnv,
1462                                 o3tl::runtimeToOUString(e.what()));
1463         }
1464 
1465         sal_Int32 nCount = rValues.getLength();
1466         uno::Sequence< uno::Any > aRet( nCount );
1467         auto aRetRange = asNonConstRange(aRet);
1468         bool bChanged = false;
1469         const beans::PropertyValue* pValues = rValues.getConstArray();
1470         for ( sal_Int32 n = 0; n < nCount; ++n )
1471         {
1472             const beans::PropertyValue& rValue = pValues[ n ];
1473             if ( rValue.Name == "ContentType" ||
1474                  rValue.Name == "MediaType" ||
1475                  rValue.Name == "IsDocument" ||
1476                  rValue.Name == "IsFolder" ||
1477                  rValue.Name == "Size" ||
1478                  rValue.Name == "CreatableContentsInfo" )
1479             {
1480                 lang::IllegalAccessException e ( u"Property is read-only!"_ustr,
1481                        getXWeak() );
1482                 aRetRange[ n ] <<= e;
1483             }
1484             else if ( rValue.Name == "Title" )
1485             {
1486                 OUString aNewTitle;
1487                 if (!( rValue.Value >>= aNewTitle ))
1488                 {
1489                     aRetRange[ n ] <<= beans::IllegalTypeException
1490                         ( u"Property value has wrong type!"_ustr,
1491                           getXWeak() );
1492                     continue;
1493                 }
1494 
1495                 if ( aNewTitle.isEmpty() )
1496                 {
1497                     aRetRange[ n ] <<= lang::IllegalArgumentException
1498                         ( u"Empty title not allowed!"_ustr,
1499                           getXWeak(), -1 );
1500                     continue;
1501 
1502                 }
1503 
1504                 setCmisProperty( "cmis:name", OUSTR_TO_STDSTR( aNewTitle ), xEnv );
1505                 bChanged = true;
1506             }
1507             else
1508             {
1509                 SAL_INFO( "ucb.ucp.cmis", "Couldn't set property: " << rValue.Name );
1510                 lang::IllegalAccessException e ( u"Property is read-only!"_ustr,
1511                        getXWeak() );
1512                 aRetRange[ n ] <<= e;
1513             }
1514         }
1515 
1516         try
1517         {
1518             if ( !m_bTransient && bChanged )
1519             {
1520                 getObject( xEnv )->updateProperties( m_pObjectProps );
1521             }
1522         }
1523         catch ( const libcmis::Exception& e )
1524         {
1525             SAL_INFO( "ucb.ucp.cmis", "Unexpected libcmis exception: " << e.what( ) );
1526             ucbhelper::cancelCommandExecution(
1527                                 ucb::IOErrorCode_GENERAL,
1528                                 uno::Sequence< uno::Any >( 0 ),
1529                                 xEnv,
1530                                 o3tl::runtimeToOUString(e.what()));
1531         }
1532 
1533         return aRet;
1534     }
1535 
feedSink(const uno::Reference<uno::XInterface> & xSink,const uno::Reference<ucb::XCommandEnvironment> & xEnv)1536     bool Content::feedSink( const uno::Reference< uno::XInterface>& xSink,
1537         const uno::Reference< ucb::XCommandEnvironment >& xEnv )
1538     {
1539         if ( !xSink.is() )
1540             return false;
1541 
1542         uno::Reference< io::XOutputStream > xOut(xSink, uno::UNO_QUERY );
1543         uno::Reference< io::XActiveDataSink > xDataSink(xSink, uno::UNO_QUERY );
1544         uno::Reference< io::XActiveDataStreamer > xDataStreamer( xSink, uno::UNO_QUERY );
1545 
1546         if ( !xOut.is() && !xDataSink.is() && ( !xDataStreamer.is() || !xDataStreamer->getStream().is() ) )
1547             return false;
1548 
1549         if ( xDataStreamer.is() && !xOut.is() )
1550             xOut = xDataStreamer->getStream()->getOutputStream();
1551 
1552         try
1553         {
1554             libcmis::Document* document = dynamic_cast< libcmis::Document* >( getObject( xEnv ).get() );
1555 
1556             if (!document)
1557                 return false;
1558 
1559             uno::Reference< io::XInputStream > xIn = new StdInputStream(document->getContentStream());
1560             if( !xIn.is( ) )
1561                 return false;
1562 
1563             if ( xDataSink.is() )
1564                 xDataSink->setInputStream( xIn );
1565             else if ( xOut.is() )
1566                 copyData( xIn, xOut );
1567         }
1568         catch ( const libcmis::Exception& e )
1569         {
1570             SAL_INFO( "ucb.ucp.cmis", "Unexpected libcmis exception: " << e.what( ) );
1571             ucbhelper::cancelCommandExecution(
1572                                 ucb::IOErrorCode_GENERAL,
1573                                 uno::Sequence< uno::Any >( 0 ),
1574                                 xEnv,
1575                                 o3tl::runtimeToOUString(e.what()));
1576         }
1577 
1578         return true;
1579     }
1580 
getProperties(const uno::Reference<ucb::XCommandEnvironment> &)1581     uno::Sequence< beans::Property > Content::getProperties(
1582             const uno::Reference< ucb::XCommandEnvironment > & )
1583     {
1584         static const beans::Property aGenericProperties[] =
1585         {
1586             beans::Property( u"IsDocument"_ustr,
1587                 -1, cppu::UnoType<bool>::get(),
1588                 beans::PropertyAttribute::BOUND | beans::PropertyAttribute::READONLY ),
1589             beans::Property( u"IsFolder"_ustr,
1590                 -1, cppu::UnoType<bool>::get(),
1591                 beans::PropertyAttribute::BOUND | beans::PropertyAttribute::READONLY ),
1592             beans::Property( u"Title"_ustr,
1593                 -1, cppu::UnoType<OUString>::get(),
1594                 beans::PropertyAttribute::BOUND ),
1595             beans::Property( u"ObjectId"_ustr,
1596                 -1, cppu::UnoType<OUString>::get(),
1597                 beans::PropertyAttribute::BOUND ),
1598             beans::Property( u"TitleOnServer"_ustr,
1599                 -1, cppu::UnoType<OUString>::get(),
1600                 beans::PropertyAttribute::BOUND ),
1601             beans::Property( u"IsReadOnly"_ustr,
1602                 -1, cppu::UnoType<bool>::get(),
1603                 beans::PropertyAttribute::BOUND | beans::PropertyAttribute::READONLY ),
1604             beans::Property( u"DateCreated"_ustr,
1605                 -1, cppu::UnoType<util::DateTime>::get(),
1606                 beans::PropertyAttribute::BOUND | beans::PropertyAttribute::READONLY ),
1607             beans::Property( u"DateModified"_ustr,
1608                 -1, cppu::UnoType<util::DateTime>::get(),
1609                 beans::PropertyAttribute::BOUND | beans::PropertyAttribute::READONLY ),
1610             beans::Property( u"Size"_ustr,
1611                 -1, cppu::UnoType<sal_Int64>::get(),
1612                 beans::PropertyAttribute::BOUND | beans::PropertyAttribute::READONLY ),
1613             beans::Property( u"CreatableContentsInfo"_ustr,
1614                 -1, cppu::UnoType<uno::Sequence< ucb::ContentInfo >>::get(),
1615                 beans::PropertyAttribute::BOUND | beans::PropertyAttribute::READONLY ),
1616             beans::Property( u"MediaType"_ustr,
1617                 -1, cppu::UnoType<OUString>::get(),
1618                 beans::PropertyAttribute::BOUND ),
1619             beans::Property( u"CmisProperties"_ustr,
1620                 -1, cppu::UnoType<uno::Sequence< document::CmisProperty>>::get(),
1621                 beans::PropertyAttribute::BOUND ),
1622             beans::Property( u"IsVersionable"_ustr,
1623                 -1, cppu::UnoType<bool>::get(),
1624                 beans::PropertyAttribute::BOUND | beans::PropertyAttribute::READONLY ),
1625             beans::Property( u"CanCheckOut"_ustr,
1626                 -1, cppu::UnoType<bool>::get(),
1627                 beans::PropertyAttribute::BOUND | beans::PropertyAttribute::READONLY ),
1628             beans::Property( u"CanCancelCheckOut"_ustr,
1629                 -1, cppu::UnoType<bool>::get(),
1630                 beans::PropertyAttribute::BOUND | beans::PropertyAttribute::READONLY ),
1631             beans::Property( u"CanCheckIn"_ustr,
1632                 -1, cppu::UnoType<bool>::get(),
1633                 beans::PropertyAttribute::BOUND | beans::PropertyAttribute::READONLY ),
1634         };
1635 
1636         const int nProps = SAL_N_ELEMENTS(aGenericProperties);
1637         return uno::Sequence< beans::Property > ( aGenericProperties, nProps );
1638     }
1639 
getCommands(const uno::Reference<ucb::XCommandEnvironment> & xEnv)1640     uno::Sequence< ucb::CommandInfo > Content::getCommands(
1641             const uno::Reference< ucb::XCommandEnvironment > & xEnv )
1642     {
1643         static const ucb::CommandInfo aCommandInfoTable[] =
1644         {
1645             // Required commands
1646             ucb::CommandInfo
1647             ( u"getCommandInfo"_ustr,
1648               -1, cppu::UnoType<void>::get() ),
1649             ucb::CommandInfo
1650             ( u"getPropertySetInfo"_ustr,
1651               -1, cppu::UnoType<void>::get() ),
1652             ucb::CommandInfo
1653             ( u"getPropertyValues"_ustr,
1654               -1, cppu::UnoType<uno::Sequence< beans::Property >>::get() ),
1655             ucb::CommandInfo
1656             ( u"setPropertyValues"_ustr,
1657               -1, cppu::UnoType<uno::Sequence< beans::PropertyValue >>::get() ),
1658 
1659             // Optional standard commands
1660             ucb::CommandInfo
1661             ( u"delete"_ustr,
1662               -1, cppu::UnoType<bool>::get() ),
1663             ucb::CommandInfo
1664             ( u"insert"_ustr,
1665               -1, cppu::UnoType<ucb::InsertCommandArgument2>::get() ),
1666             ucb::CommandInfo
1667             ( u"open"_ustr,
1668               -1, cppu::UnoType<ucb::OpenCommandArgument2>::get() ),
1669 
1670             // Mandatory CMIS-only commands
1671             ucb::CommandInfo ( u"checkout"_ustr, -1, cppu::UnoType<void>::get() ),
1672             ucb::CommandInfo ( u"cancelCheckout"_ustr, -1, cppu::UnoType<void>::get() ),
1673             ucb::CommandInfo ( u"checkIn"_ustr, -1,
1674                     cppu::UnoType<ucb::TransferInfo>::get() ),
1675             ucb::CommandInfo ( u"updateProperties"_ustr, -1, cppu::UnoType<void>::get() ),
1676             ucb::CommandInfo
1677             ( u"getAllVersions"_ustr,
1678               -1, cppu::UnoType<uno::Sequence< document::CmisVersion >>::get() ),
1679 
1680 
1681             // Folder Only, omitted if not a folder
1682             ucb::CommandInfo
1683             ( u"transfer"_ustr,
1684               -1, cppu::UnoType<ucb::TransferInfo>::get() ),
1685             ucb::CommandInfo
1686             ( u"createNewContent"_ustr,
1687               -1, cppu::UnoType<ucb::ContentInfo>::get() )
1688         };
1689 
1690         const int nProps = SAL_N_ELEMENTS( aCommandInfoTable );
1691         return uno::Sequence< ucb::CommandInfo >(aCommandInfoTable, isFolder( xEnv ) ? nProps : nProps - 2);
1692     }
1693 
getParentURL()1694     OUString Content::getParentURL( )
1695     {
1696         SAL_INFO( "ucb.ucp.cmis", "Content::getParentURL()" );
1697         OUString parentUrl = u"/"_ustr;
1698         if ( m_sObjectPath == "/" )
1699             return parentUrl;
1700         else
1701         {
1702             INetURLObject aUrl( m_sURL );
1703             if ( aUrl.getSegmentCount( ) > 0 )
1704             {
1705                 URL aCmisUrl( m_sURL );
1706                 aUrl.removeSegment( );
1707                 aCmisUrl.setObjectPath( aUrl.GetURLPath( INetURLObject::DecodeMechanism::WithCharset ) );
1708                 parentUrl = aCmisUrl.asString( );
1709             }
1710         }
1711         return parentUrl;
1712     }
1713 
1714     XTYPEPROVIDER_COMMON_IMPL( Content );
1715 
acquire()1716     void SAL_CALL Content::acquire() noexcept
1717     {
1718         ContentImplHelper::acquire();
1719     }
1720 
release()1721     void SAL_CALL Content::release() noexcept
1722     {
1723         ContentImplHelper::release();
1724     }
1725 
queryInterface(const uno::Type & rType)1726     uno::Any SAL_CALL Content::queryInterface( const uno::Type & rType )
1727     {
1728         uno::Any aRet = cppu::queryInterface( rType, static_cast< ucb::XContentCreator * >( this ) );
1729         return aRet.hasValue() ? aRet : ContentImplHelper::queryInterface(rType);
1730     }
1731 
getImplementationName()1732     OUString SAL_CALL Content::getImplementationName()
1733     {
1734        return u"com.sun.star.comp.CmisContent"_ustr;
1735     }
1736 
getSupportedServiceNames()1737     uno::Sequence< OUString > SAL_CALL Content::getSupportedServiceNames()
1738     {
1739            uno::Sequence<OUString> aSNS { u"com.sun.star.ucb.CmisContent"_ustr };
1740            return aSNS;
1741     }
1742 
getContentType()1743     OUString SAL_CALL Content::getContentType()
1744     {
1745         OUString sRet;
1746         try
1747         {
1748             if (isFolder( uno::Reference< ucb::XCommandEnvironment >() ))
1749                 sRet = CMIS_FOLDER_TYPE;
1750             else
1751                 sRet = CMIS_FILE_TYPE;
1752         }
1753         catch (const uno::RuntimeException&)
1754         {
1755             throw;
1756         }
1757         catch (const uno::Exception& e)
1758         {
1759             uno::Any a(cppu::getCaughtException());
1760             throw lang::WrappedTargetRuntimeException(
1761                 "wrapped Exception " + e.Message,
1762                 uno::Reference<uno::XInterface>(), a);
1763         }
1764         return sRet;
1765     }
1766 
execute(const ucb::Command & aCommand,sal_Int32,const uno::Reference<ucb::XCommandEnvironment> & xEnv)1767     uno::Any SAL_CALL Content::execute(
1768         const ucb::Command& aCommand,
1769         sal_Int32 /*CommandId*/,
1770         const uno::Reference< ucb::XCommandEnvironment >& xEnv )
1771     {
1772         SAL_INFO( "ucb.ucp.cmis", "Content::execute( ) - " << aCommand.Name );
1773         uno::Any aRet;
1774 
1775         if ( aCommand.Name == "getPropertyValues" )
1776         {
1777             uno::Sequence< beans::Property > Properties;
1778             if ( !( aCommand.Argument >>= Properties ) )
1779                 ucbhelper::cancelCommandExecution ( getBadArgExcept (), xEnv );
1780             aRet <<= getPropertyValues( Properties, xEnv );
1781         }
1782         else if ( aCommand.Name == "getPropertySetInfo" )
1783             aRet <<= getPropertySetInfo( xEnv, false );
1784         else if ( aCommand.Name == "getCommandInfo" )
1785             aRet <<= getCommandInfo( xEnv, false );
1786         else if ( aCommand.Name == "open" )
1787         {
1788             ucb::OpenCommandArgument2 aOpenCommand;
1789             if ( !( aCommand.Argument >>= aOpenCommand ) )
1790                 ucbhelper::cancelCommandExecution ( getBadArgExcept (), xEnv );
1791             aRet = open( aOpenCommand, xEnv );
1792         }
1793         else if ( aCommand.Name == "transfer" )
1794         {
1795             ucb::TransferInfo transferArgs;
1796             if ( !( aCommand.Argument >>= transferArgs ) )
1797                 ucbhelper::cancelCommandExecution ( getBadArgExcept (), xEnv );
1798             transfer( transferArgs, xEnv );
1799         }
1800         else if ( aCommand.Name == "setPropertyValues" )
1801         {
1802             uno::Sequence< beans::PropertyValue > aProperties;
1803             if ( !( aCommand.Argument >>= aProperties ) || !aProperties.hasElements() )
1804                 ucbhelper::cancelCommandExecution ( getBadArgExcept (), xEnv );
1805             aRet <<= setPropertyValues( aProperties, xEnv );
1806         }
1807         else if (aCommand.Name == "createNewContent"
1808                  && isFolder( xEnv ) )
1809         {
1810             ucb::ContentInfo arg;
1811             if ( !( aCommand.Argument >>= arg ) )
1812                     ucbhelper::cancelCommandExecution ( getBadArgExcept (), xEnv );
1813             aRet <<= createNewContent( arg );
1814         }
1815         else if ( aCommand.Name == "insert" )
1816         {
1817             ucb::InsertCommandArgument2 arg;
1818             if ( !( aCommand.Argument >>= arg ) )
1819             {
1820                 ucb::InsertCommandArgument insertArg;
1821                 if ( !( aCommand.Argument >>= insertArg ) )
1822                     ucbhelper::cancelCommandExecution ( getBadArgExcept (), xEnv );
1823 
1824                 arg.Data = insertArg.Data;
1825                 arg.ReplaceExisting = insertArg.ReplaceExisting;
1826             }
1827             // store the document id
1828             m_sObjectId = arg.DocumentId;
1829             insert( arg.Data, arg.ReplaceExisting, arg.MimeType, xEnv );
1830         }
1831         else if ( aCommand.Name == "delete" )
1832         {
1833             try
1834             {
1835                 if ( !isFolder( xEnv ) )
1836                 {
1837                     getObject( xEnv )->remove( );
1838                 }
1839                 else
1840                 {
1841                     libcmis::Folder* folder = dynamic_cast< libcmis::Folder* >( getObject( xEnv ).get() );
1842                     if (folder)
1843                         folder->removeTree( );
1844                 }
1845             }
1846             catch ( const libcmis::Exception& e )
1847             {
1848                 SAL_INFO( "ucb.ucp.cmis", "Unexpected libcmis exception: " << e.what( ) );
1849                 ucbhelper::cancelCommandExecution(
1850                                     ucb::IOErrorCode_GENERAL,
1851                                     uno::Sequence< uno::Any >( 0 ),
1852                                     xEnv,
1853                                     o3tl::runtimeToOUString(e.what()));
1854             }
1855         }
1856         else if ( aCommand.Name == "checkout" )
1857         {
1858             aRet <<= checkOut( xEnv );
1859         }
1860         else if ( aCommand.Name == "cancelCheckout" )
1861         {
1862             aRet <<= cancelCheckOut( xEnv );
1863         }
1864         else if ( aCommand.Name == "checkin" )
1865         {
1866             ucb::CheckinArgument aArg;
1867             if ( !( aCommand.Argument >>= aArg ) )
1868             {
1869                 ucbhelper::cancelCommandExecution ( getBadArgExcept(), xEnv );
1870             }
1871             aRet <<= checkIn( aArg, xEnv );
1872         }
1873         else if ( aCommand.Name == "getAllVersions" )
1874         {
1875             aRet <<= getAllVersions( xEnv );
1876         }
1877         else if ( aCommand.Name == "updateProperties" )
1878         {
1879             updateProperties( aCommand.Argument, xEnv );
1880         }
1881         else
1882         {
1883             SAL_INFO( "ucb.ucp.cmis", "Unknown command to execute" );
1884 
1885             ucbhelper::cancelCommandExecution
1886                 ( uno::Any( ucb::UnsupportedCommandException
1887                   ( OUString(),
1888                     getXWeak() ) ),
1889                   xEnv );
1890         }
1891 
1892         return aRet;
1893     }
1894 
abort(sal_Int32)1895     void SAL_CALL Content::abort( sal_Int32 /*CommandId*/ )
1896     {
1897         SAL_INFO( "ucb.ucp.cmis", "TODO - Content::abort()" );
1898         // TODO Implement me
1899     }
1900 
queryCreatableContentsInfo()1901     uno::Sequence< ucb::ContentInfo > SAL_CALL Content::queryCreatableContentsInfo()
1902     {
1903         return queryCreatableContentsInfo( uno::Reference< ucb::XCommandEnvironment >() );
1904     }
1905 
createNewContent(const ucb::ContentInfo & Info)1906     uno::Reference< ucb::XContent > SAL_CALL Content::createNewContent(
1907             const ucb::ContentInfo& Info )
1908     {
1909         bool create_document;
1910 
1911         if ( Info.Type == CMIS_FILE_TYPE )
1912             create_document = true;
1913         else if ( Info.Type == CMIS_FOLDER_TYPE )
1914             create_document = false;
1915         else
1916         {
1917             SAL_INFO( "ucb.ucp.cmis", "Unknown type of content to create" );
1918             return uno::Reference< ucb::XContent >();
1919         }
1920 
1921         OUString sParentURL = m_xIdentifier->getContentIdentifier();
1922 
1923         // Set the parent URL for the transient objects
1924         uno::Reference< ucb::XContentIdentifier > xId(new ::ucbhelper::ContentIdentifier(sParentURL));
1925 
1926         try
1927         {
1928             return new ::cmis::Content( m_xContext, m_pProvider, xId, !create_document );
1929         }
1930         catch ( ucb::ContentCreationException & )
1931         {
1932             return uno::Reference< ucb::XContent >();
1933         }
1934     }
1935 
getTypes()1936     uno::Sequence< uno::Type > SAL_CALL Content::getTypes()
1937     {
1938         try
1939         {
1940             if ( isFolder( uno::Reference< ucb::XCommandEnvironment >() ) )
1941             {
1942                 static cppu::OTypeCollection s_aFolderCollection
1943                     (CPPU_TYPE_REF( lang::XTypeProvider ),
1944                      CPPU_TYPE_REF( lang::XServiceInfo ),
1945                      CPPU_TYPE_REF( lang::XComponent ),
1946                      CPPU_TYPE_REF( ucb::XContent ),
1947                      CPPU_TYPE_REF( ucb::XCommandProcessor ),
1948                      CPPU_TYPE_REF( beans::XPropertiesChangeNotifier ),
1949                      CPPU_TYPE_REF( ucb::XCommandInfoChangeNotifier ),
1950                      CPPU_TYPE_REF( beans::XPropertyContainer ),
1951                      CPPU_TYPE_REF( beans::XPropertySetInfoChangeNotifier ),
1952                      CPPU_TYPE_REF( container::XChild ),
1953                      CPPU_TYPE_REF( ucb::XContentCreator ) );
1954                 return s_aFolderCollection.getTypes();
1955             }
1956         }
1957         catch (const uno::RuntimeException&)
1958         {
1959             throw;
1960         }
1961         catch (const uno::Exception& e)
1962         {
1963             uno::Any a(cppu::getCaughtException());
1964             throw lang::WrappedTargetRuntimeException(
1965                 "wrapped Exception " + e.Message,
1966                 uno::Reference<uno::XInterface>(), a);
1967         }
1968 
1969         static cppu::OTypeCollection s_aFileCollection
1970             (CPPU_TYPE_REF( lang::XTypeProvider ),
1971              CPPU_TYPE_REF( lang::XServiceInfo ),
1972              CPPU_TYPE_REF( lang::XComponent ),
1973              CPPU_TYPE_REF( ucb::XContent ),
1974              CPPU_TYPE_REF( ucb::XCommandProcessor ),
1975              CPPU_TYPE_REF( beans::XPropertiesChangeNotifier ),
1976              CPPU_TYPE_REF( ucb::XCommandInfoChangeNotifier ),
1977              CPPU_TYPE_REF( beans::XPropertyContainer ),
1978              CPPU_TYPE_REF( beans::XPropertySetInfoChangeNotifier ),
1979              CPPU_TYPE_REF( container::XChild ) );
1980 
1981         return s_aFileCollection.getTypes();
1982     }
1983 
queryCreatableContentsInfo(const uno::Reference<ucb::XCommandEnvironment> & xEnv)1984     uno::Sequence< ucb::ContentInfo > Content::queryCreatableContentsInfo(
1985         const uno::Reference< ucb::XCommandEnvironment >& xEnv)
1986     {
1987         try
1988         {
1989             if ( isFolder( xEnv ) )
1990             {
1991 
1992                 // Minimum set of props we really need
1993                 uno::Sequence< beans::Property > props
1994                 {
1995                     {
1996                         u"Title"_ustr,
1997                         -1,
1998                         cppu::UnoType<OUString>::get(),
1999                         beans::PropertyAttribute::MAYBEVOID | beans::PropertyAttribute::BOUND
2000                     }
2001                 };
2002 
2003                 return
2004                 {
2005                     {
2006                         CMIS_FILE_TYPE,
2007                         ( ucb::ContentInfoAttribute::INSERT_WITH_INPUTSTREAM |
2008                                       ucb::ContentInfoAttribute::KIND_DOCUMENT ),
2009                         props
2010                     },
2011                     {
2012                         CMIS_FOLDER_TYPE,
2013                         ucb::ContentInfoAttribute::KIND_FOLDER,
2014                         props
2015                     }
2016                 };
2017             }
2018         }
2019         catch (const uno::RuntimeException&)
2020         {
2021             throw;
2022         }
2023         catch (const uno::Exception& e)
2024         {
2025             uno::Any a(cppu::getCaughtException());
2026             throw lang::WrappedTargetRuntimeException(
2027                 "wrapped Exception " + e.Message,
2028                 uno::Reference<uno::XInterface>(), a);
2029         }
2030         return {};
2031     }
2032 
getChildren()2033     std::vector< uno::Reference< ucb::XContent > > Content::getChildren( )
2034     {
2035         std::vector< uno::Reference< ucb::XContent > > results;
2036         SAL_INFO( "ucb.ucp.cmis", "Content::getChildren() " << m_sURL );
2037 
2038         libcmis::FolderPtr pFolder = boost::dynamic_pointer_cast< libcmis::Folder >( getObject( uno::Reference< ucb::XCommandEnvironment >() ) );
2039         if ( nullptr != pFolder )
2040         {
2041             // Get the children from pObject
2042             try
2043             {
2044                 std::vector< libcmis::ObjectPtr > children = pFolder->getChildren( );
2045 
2046                 // Loop over the results
2047                 for ( const auto& rChild : children )
2048                 {
2049                     // TODO Cache the objects
2050 
2051                     INetURLObject aURL( m_sURL );
2052                     OUString sUser = aURL.GetUser( INetURLObject::DecodeMechanism::WithCharset );
2053 
2054                     URL aUrl( m_sURL );
2055                     OUString sPath( m_sObjectPath );
2056                     if ( !sPath.endsWith("/") )
2057                         sPath += "/";
2058                     sPath += STD_TO_OUSTR( rChild->getName( ) );
2059                     OUString sId = STD_TO_OUSTR( rChild->getId( ) );
2060 
2061                     aUrl.setObjectId( sId );
2062                     aUrl.setObjectPath( sPath );
2063                     aUrl.setUsername( sUser );
2064 
2065                     uno::Reference< ucb::XContentIdentifier > xId = new ucbhelper::ContentIdentifier( aUrl.asString( ) );
2066                     uno::Reference< ucb::XContent > xContent = new Content( m_xContext, m_pProvider, xId, rChild );
2067 
2068                     results.push_back( xContent );
2069                 }
2070             }
2071             catch ( const libcmis::Exception& e )
2072             {
2073                 SAL_INFO( "ucb.ucp.cmis", "Exception thrown: " << e.what() );
2074             }
2075         }
2076 
2077         return results;
2078     }
2079 
setCmisProperty(const std::string & rName,const std::string & rValue,const uno::Reference<ucb::XCommandEnvironment> & xEnv)2080     void Content::setCmisProperty(const std::string& rName, const std::string& rValue, const uno::Reference< ucb::XCommandEnvironment >& xEnv )
2081     {
2082         if ( !getObjectType( xEnv ).get( ) )
2083             return;
2084 
2085         std::map< std::string, libcmis::PropertyPtr >::iterator propIt = m_pObjectProps.find(rName);
2086 
2087         if ( propIt == m_pObjectProps.end( ) && getObjectType( xEnv ).get( ) )
2088         {
2089             std::map< std::string, libcmis::PropertyTypePtr > propsTypes = getObjectType( xEnv )->getPropertiesTypes( );
2090             std::map< std::string, libcmis::PropertyTypePtr >::iterator typeIt = propsTypes.find(rName);
2091 
2092             if ( typeIt != propsTypes.end( ) )
2093             {
2094                 libcmis::PropertyPtr property( new libcmis::Property( typeIt->second, { rValue }) );
2095                 m_pObjectProps.insert(std::pair< std::string, libcmis::PropertyPtr >(rName, property));
2096             }
2097         }
2098         else if ( propIt != m_pObjectProps.end( ) )
2099         {
2100             propIt->second->setValues( { rValue } );
2101         }
2102     }
2103 }
2104 
2105 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */
2106