/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ /* * This file is part of the LibreOffice project. * * This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. * * This file incorporates work covered by the following license notice: * * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed * with this work for additional information regarding copyright * ownership. The ASF licenses this file to you under the Apache * License, Version 2.0 (the "License"); you may not use this file * except in compliance with the License. You may obtain a copy of * the License at http://www.apache.org/licenses/LICENSE-2.0 . */ /************************************************************************** TODO ************************************************************************** - optimize transfer command. "Move" should be implementable much more efficient! ************************************************************************** - Root Folder vs. 'normal' Folder - root doesn't support command 'delete' - root doesn't support command 'insert' - root needs not created via XContentCreator - queryContent with root folder id ( HIERARCHY_ROOT_FOLDER_URL ) always returns a value != 0 - root has no parent. *************************************************************************/ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "hierarchycontent.hxx" #include "hierarchyprovider.hxx" #include "dynamicresultset.hxx" #include "hierarchyuri.hxx" #include "../inc/urihelper.hxx" using namespace com::sun::star; using namespace hierarchy_ucp; // HierarchyContent Implementation. // static ( "virtual" ctor ) rtl::Reference HierarchyContent::create( const uno::Reference< uno::XComponentContext >& rxContext, HierarchyContentProvider* pProvider, const uno::Reference< ucb::XContentIdentifier >& Identifier ) { // Fail, if content does not exist. HierarchyContentProperties aProps; if ( !loadData( rxContext, pProvider, Identifier, aProps ) ) return nullptr; return new HierarchyContent( rxContext, pProvider, Identifier, aProps ); } // static ( "virtual" ctor ) rtl::Reference HierarchyContent::create( const uno::Reference< uno::XComponentContext >& rxContext, HierarchyContentProvider* pProvider, const uno::Reference< ucb::XContentIdentifier >& Identifier, const ucb::ContentInfo& Info ) { if ( Info.Type.isEmpty() ) return nullptr; if ( Info.Type != HIERARCHY_FOLDER_CONTENT_TYPE && Info.Type != HIERARCHY_LINK_CONTENT_TYPE ) return nullptr; return new HierarchyContent( rxContext, pProvider, Identifier, Info ); } HierarchyContent::HierarchyContent( const uno::Reference< uno::XComponentContext >& rxContext, HierarchyContentProvider* pProvider, const uno::Reference< ucb::XContentIdentifier >& Identifier, const HierarchyContentProperties& rProps ) : ContentImplHelper( rxContext, pProvider, Identifier ), m_aProps( rProps ), m_eState( PERSISTENT ), m_pProvider( pProvider ), m_bCheckedReadOnly( false ), m_bIsReadOnly( true ) { setKind( Identifier ); } HierarchyContent::HierarchyContent( const uno::Reference< uno::XComponentContext >& rxContext, HierarchyContentProvider* pProvider, const uno::Reference< ucb::XContentIdentifier >& Identifier, const ucb::ContentInfo& Info ) : ContentImplHelper( rxContext, pProvider, Identifier ), m_aProps( Info.Type == HIERARCHY_FOLDER_CONTENT_TYPE ? HierarchyEntryData::FOLDER : HierarchyEntryData::LINK ), m_eState( TRANSIENT ), m_pProvider( pProvider ), m_bCheckedReadOnly( false ), m_bIsReadOnly( true ) { setKind( Identifier ); } // virtual HierarchyContent::~HierarchyContent() { } // XInterface methods. // virtual void SAL_CALL HierarchyContent::acquire() noexcept { ContentImplHelper::acquire(); } // virtual void SAL_CALL HierarchyContent::release() noexcept { ContentImplHelper::release(); } // virtual uno::Any SAL_CALL HierarchyContent::queryInterface( const uno::Type & rType ) { uno::Any aRet = ContentImplHelper::queryInterface( rType ); if ( !aRet.hasValue() ) { // Note: isReadOnly may be relative expensive. So avoid calling it // unless it is really necessary. aRet = cppu::queryInterface( rType, static_cast< ucb::XContentCreator * >( this ) ); if ( aRet.hasValue() ) { if ( !isFolder() || isReadOnly() ) return uno::Any(); } } return aRet; } // XTypeProvider methods. XTYPEPROVIDER_COMMON_IMPL( HierarchyContent ); // virtual uno::Sequence< uno::Type > SAL_CALL HierarchyContent::getTypes() { if ( isFolder() && !isReadOnly() ) { static cppu::OTypeCollection s_aFolderTypes( CPPU_TYPE_REF( lang::XTypeProvider ), CPPU_TYPE_REF( lang::XServiceInfo ), CPPU_TYPE_REF( lang::XComponent ), CPPU_TYPE_REF( ucb::XContent ), CPPU_TYPE_REF( ucb::XCommandProcessor ), CPPU_TYPE_REF( beans::XPropertiesChangeNotifier ), CPPU_TYPE_REF( ucb::XCommandInfoChangeNotifier ), CPPU_TYPE_REF( beans::XPropertyContainer ), CPPU_TYPE_REF( beans::XPropertySetInfoChangeNotifier ), CPPU_TYPE_REF( container::XChild ), CPPU_TYPE_REF( ucb::XContentCreator ) ); return s_aFolderTypes.getTypes(); } else { static cppu::OTypeCollection s_aDocumentTypes( CPPU_TYPE_REF( lang::XTypeProvider ), CPPU_TYPE_REF( lang::XServiceInfo ), CPPU_TYPE_REF( lang::XComponent ), CPPU_TYPE_REF( ucb::XContent ), CPPU_TYPE_REF( ucb::XCommandProcessor ), CPPU_TYPE_REF( beans::XPropertiesChangeNotifier ), CPPU_TYPE_REF( ucb::XCommandInfoChangeNotifier ), CPPU_TYPE_REF( beans::XPropertyContainer ), CPPU_TYPE_REF( beans::XPropertySetInfoChangeNotifier ), CPPU_TYPE_REF( container::XChild ) ); return s_aDocumentTypes.getTypes(); } } // XServiceInfo methods. // virtual OUString SAL_CALL HierarchyContent::getImplementationName() { return "com.sun.star.comp.ucb.HierarchyContent"; } // virtual uno::Sequence< OUString > SAL_CALL HierarchyContent::getSupportedServiceNames() { uno::Sequence< OUString > aSNS( 1 ); if ( m_eKind == LINK ) aSNS.getArray()[ 0 ] = "com.sun.star.ucb.HierarchyLinkContent"; else if ( m_eKind == FOLDER ) aSNS.getArray()[ 0 ] = "com.sun.star.ucb.HierarchyFolderContent"; else aSNS.getArray()[ 0 ] = "com.sun.star.ucb.HierarchyRootFolderContent"; return aSNS; } // XContent methods. // virtual OUString SAL_CALL HierarchyContent::getContentType() { return m_aProps.getContentType(); } // virtual uno::Reference< ucb::XContentIdentifier > SAL_CALL HierarchyContent::getIdentifier() { // Transient? if ( m_eState == TRANSIENT ) { // Transient contents have no identifier. return uno::Reference< ucb::XContentIdentifier >(); } return ContentImplHelper::getIdentifier(); } // XCommandProcessor methods. // virtual uno::Any SAL_CALL HierarchyContent::execute( const ucb::Command& aCommand, sal_Int32 /*CommandId*/, const uno::Reference< ucb::XCommandEnvironment >& Environment ) { uno::Any aRet; if ( aCommand.Name == "getPropertyValues" ) { // getPropertyValues uno::Sequence< beans::Property > Properties; if ( !( aCommand.Argument >>= Properties ) ) { ucbhelper::cancelCommandExecution( uno::Any( lang::IllegalArgumentException( "Wrong argument type!", static_cast< cppu::OWeakObject * >( this ), -1 ) ), Environment ); // Unreachable } aRet <<= getPropertyValues( Properties ); } else if ( aCommand.Name == "setPropertyValues" ) { // setPropertyValues uno::Sequence< beans::PropertyValue > aProperties; if ( !( aCommand.Argument >>= aProperties ) ) { ucbhelper::cancelCommandExecution( uno::Any( lang::IllegalArgumentException( "Wrong argument type!", static_cast< cppu::OWeakObject * >( this ), -1 ) ), Environment ); // Unreachable } if ( !aProperties.hasElements() ) { ucbhelper::cancelCommandExecution( uno::Any( lang::IllegalArgumentException( "No properties!", static_cast< cppu::OWeakObject * >( this ), -1 ) ), Environment ); // Unreachable } aRet <<= setPropertyValues( aProperties, Environment ); } else if ( aCommand.Name == "getPropertySetInfo" ) { // getPropertySetInfo aRet <<= getPropertySetInfo( Environment ); } else if ( aCommand.Name == "getCommandInfo" ) { // getCommandInfo aRet <<= getCommandInfo( Environment ); } else if ( aCommand.Name == "open" && isFolder() ) { // open command for a folder content ucb::OpenCommandArgument2 aOpenCommand; if ( !( aCommand.Argument >>= aOpenCommand ) ) { ucbhelper::cancelCommandExecution( uno::Any( lang::IllegalArgumentException( "Wrong argument type!", static_cast< cppu::OWeakObject * >( this ), -1 ) ), Environment ); // Unreachable } uno::Reference< ucb::XDynamicResultSet > xSet = new DynamicResultSet( m_xContext, this, aOpenCommand ); aRet <<= xSet; } else if ( aCommand.Name == "insert" && ( m_eKind != ROOT ) && !isReadOnly() ) { // insert // ( Not available at root folder ) ucb::InsertCommandArgument aArg; if ( !( aCommand.Argument >>= aArg ) ) { ucbhelper::cancelCommandExecution( uno::Any( lang::IllegalArgumentException( "Wrong argument type!", static_cast< cppu::OWeakObject * >( this ), -1 ) ), Environment ); // Unreachable } sal_Int32 nNameClash = aArg.ReplaceExisting ? ucb::NameClash::OVERWRITE : ucb::NameClash::ERROR; insert( nNameClash, Environment ); } else if ( aCommand.Name == "delete" && ( m_eKind != ROOT ) && !isReadOnly() ) { // delete // ( Not available at root folder ) bool bDeletePhysical = false; aCommand.Argument >>= bDeletePhysical; destroy( bDeletePhysical, Environment ); // Remove own and all children's persistent data. if ( !removeData() ) { uno::Sequence aArgs(comphelper::InitAnyPropertySequence( { {"Uri", uno::Any(m_xIdentifier->getContentIdentifier())} })); ucbhelper::cancelCommandExecution( ucb::IOErrorCode_CANT_WRITE, aArgs, Environment, "Cannot remove persistent data!", this ); // Unreachable } // Remove own and all children's Additional Core Properties. removeAdditionalPropertySet(); } else if ( aCommand.Name == "transfer" && isFolder() && !isReadOnly() ) { // transfer // ( Not available at link objects ) ucb::TransferInfo aInfo; if ( !( aCommand.Argument >>= aInfo ) ) { OSL_FAIL( "Wrong argument type!" ); ucbhelper::cancelCommandExecution( uno::Any( lang::IllegalArgumentException( "Wrong argument type!", static_cast< cppu::OWeakObject * >( this ), -1 ) ), Environment ); // Unreachable } transfer( aInfo, Environment ); } else if ( aCommand.Name == "createNewContent" && isFolder() && !isReadOnly() ) { // createNewContent // ( Not available at link objects ) ucb::ContentInfo aInfo; if ( !( aCommand.Argument >>= aInfo ) ) { OSL_FAIL( "Wrong argument type!" ); ucbhelper::cancelCommandExecution( uno::Any( lang::IllegalArgumentException( "Wrong argument type!", static_cast< cppu::OWeakObject * >( this ), -1 ) ), Environment ); // Unreachable } aRet <<= createNewContent( aInfo ); } else { // Unsupported command ucbhelper::cancelCommandExecution( uno::Any( ucb::UnsupportedCommandException( OUString(), static_cast< cppu::OWeakObject * >( this ) ) ), Environment ); // Unreachable } return aRet; } // virtual void SAL_CALL HierarchyContent::abort( sal_Int32 /*CommandId*/ ) { // @@@ Generally, no action takes much time... } // XContentCreator methods. // virtual uno::Sequence< ucb::ContentInfo > SAL_CALL HierarchyContent::queryCreatableContentsInfo() { return m_aProps.getCreatableContentsInfo(); } // virtual uno::Reference< ucb::XContent > SAL_CALL HierarchyContent::createNewContent( const ucb::ContentInfo& Info ) { if ( isFolder() ) { osl::Guard< osl::Mutex > aGuard( m_aMutex ); if ( Info.Type.isEmpty() ) return uno::Reference< ucb::XContent >(); bool bCreateFolder = Info.Type == HIERARCHY_FOLDER_CONTENT_TYPE; if ( !bCreateFolder && Info.Type != HIERARCHY_LINK_CONTENT_TYPE ) return uno::Reference< ucb::XContent >(); OUString aURL = m_xIdentifier->getContentIdentifier(); OSL_ENSURE( !aURL.isEmpty(), "HierarchyContent::createNewContent - empty identifier!" ); if ( ( aURL.lastIndexOf( '/' ) + 1 ) != aURL.getLength() ) aURL += "/"; if ( bCreateFolder ) aURL += "New_Folder"; else aURL += "New_Link"; uno::Reference< ucb::XContentIdentifier > xId = new ::ucbhelper::ContentIdentifier( aURL ); return create( m_xContext, m_pProvider, xId, Info ); } else { OSL_FAIL( "createNewContent called on non-folder object!" ); return uno::Reference< ucb::XContent >(); } } // virtual OUString HierarchyContent::getParentURL() { HierarchyUri aUri( m_xIdentifier->getContentIdentifier() ); return aUri.getParentUri(); } //static bool HierarchyContent::hasData( const uno::Reference< uno::XComponentContext >& rxContext, HierarchyContentProvider* pProvider, const uno::Reference< ucb::XContentIdentifier >& Identifier ) { OUString aURL = Identifier->getContentIdentifier(); // Am I a root folder? HierarchyUri aUri( aURL ); if ( aUri.isRootFolder() ) { // hasData must always return 'true' for root folder // even if no persistent data exist!!! return true; } return HierarchyEntry( rxContext, pProvider, aURL ).hasData(); } //static bool HierarchyContent::loadData( const uno::Reference< uno::XComponentContext >& rxContext, HierarchyContentProvider* pProvider, const uno::Reference< ucb::XContentIdentifier >& Identifier, HierarchyContentProperties& rProps ) { OUString aURL = Identifier->getContentIdentifier(); // Am I a root folder? HierarchyUri aUri( aURL ); if ( aUri.isRootFolder() ) { rProps = HierarchyContentProperties( HierarchyEntryData::FOLDER ); } else { HierarchyEntry aEntry( rxContext, pProvider, aURL ); HierarchyEntryData aData; if ( !aEntry.getData( aData ) ) return false; rProps = HierarchyContentProperties( aData ); } return true; } bool HierarchyContent::storeData() { HierarchyEntry aEntry( m_xContext, m_pProvider, m_xIdentifier->getContentIdentifier() ); return aEntry.setData( m_aProps.getHierarchyEntryData() ); } void HierarchyContent::renameData( const uno::Reference< ucb::XContentIdentifier >& xOldId, const uno::Reference< ucb::XContentIdentifier >& xNewId ) { HierarchyEntry aEntry( m_xContext, m_pProvider, xOldId->getContentIdentifier() ); aEntry.move( xNewId->getContentIdentifier(), m_aProps.getHierarchyEntryData() ); } bool HierarchyContent::removeData() { HierarchyEntry aEntry( m_xContext, m_pProvider, m_xIdentifier->getContentIdentifier() ); return aEntry.remove(); } void HierarchyContent::setKind( const uno::Reference< ucb::XContentIdentifier >& Identifier ) { if ( m_aProps.getIsFolder() ) { // Am I a root folder? HierarchyUri aUri( Identifier->getContentIdentifier() ); if ( aUri.isRootFolder() ) m_eKind = ROOT; else m_eKind = FOLDER; } else m_eKind = LINK; } bool HierarchyContent::isReadOnly() { if ( !m_bCheckedReadOnly ) { osl::Guard< osl::Mutex > aGuard( m_aMutex ); if ( !m_bCheckedReadOnly ) { m_bCheckedReadOnly = true; m_bIsReadOnly = true; HierarchyUri aUri( m_xIdentifier->getContentIdentifier() ); uno::Reference< lang::XMultiServiceFactory > xConfigProv = m_pProvider->getConfigProvider( aUri.getService() ); if ( xConfigProv.is() ) { uno::Sequence< OUString > aNames = xConfigProv->getAvailableServiceNames(); m_bIsReadOnly = comphelper::findValue(aNames, "com.sun.star.ucb.HierarchyDataReadWriteAccess") == -1; } } } return m_bIsReadOnly; } uno::Reference< ucb::XContentIdentifier > HierarchyContent::makeNewIdentifier( const OUString& rTitle ) { osl::Guard< osl::Mutex > aGuard( m_aMutex ); // Assemble new content identifier... HierarchyUri aUri( m_xIdentifier->getContentIdentifier() ); OUString aNewURL = aUri.getParentUri() + "/"; aNewURL += ::ucb_impl::urihelper::encodeSegment( rTitle ); return uno::Reference< ucb::XContentIdentifier >( new ::ucbhelper::ContentIdentifier( aNewURL ) ); } void HierarchyContent::queryChildren( HierarchyContentRefVector& rChildren ) { if ( ( m_eKind != FOLDER ) && ( m_eKind != ROOT ) ) return; // Obtain a list with a snapshot of all currently instantiated contents // from provider and extract the contents which are direct children // of this content. ::ucbhelper::ContentRefList aAllContents; m_xProvider->queryExistingContents( aAllContents ); OUString aURL = m_xIdentifier->getContentIdentifier(); sal_Int32 nURLPos = aURL.lastIndexOf( '/' ); if ( nURLPos != ( aURL.getLength() - 1 ) ) { // No trailing slash found. Append. aURL += "/"; } sal_Int32 nLen = aURL.getLength(); for ( const auto& rContent : aAllContents ) { ::ucbhelper::ContentImplHelperRef xChild = rContent; OUString aChildURL = xChild->getIdentifier()->getContentIdentifier(); // Is aURL a prefix of aChildURL? if ( ( aChildURL.getLength() > nLen ) && ( aChildURL.startsWith( aURL ) ) ) { sal_Int32 nPos = aChildURL.indexOf( '/', nLen ); if ( ( nPos == -1 ) || ( nPos == ( aChildURL.getLength() - 1 ) ) ) { // No further slashes/ only a final slash. It's a child! rChildren.emplace_back( static_cast< HierarchyContent * >( xChild.get() ) ); } } } } bool HierarchyContent::exchangeIdentity( const uno::Reference< ucb::XContentIdentifier >& xNewId ) { if ( !xNewId.is() ) return false; osl::ClearableGuard< osl::Mutex > aGuard( m_aMutex ); uno::Reference< ucb::XContent > xThis = this; // Already persistent? if ( m_eState != PERSISTENT ) { OSL_FAIL( "HierarchyContent::exchangeIdentity - Not persistent!" ); return false; } // Am I the root folder? if ( m_eKind == ROOT ) { OSL_FAIL( "HierarchyContent::exchangeIdentity - " "Not supported by root folder!" ); return false; } // Exchange own identity. // Fail, if a content with given id already exists. if ( !hasData( xNewId ) ) { OUString aOldURL = m_xIdentifier->getContentIdentifier(); aGuard.clear(); if ( exchange( xNewId ) ) { if ( m_eKind == FOLDER ) { // Process instantiated children... HierarchyContentRefVector aChildren; queryChildren( aChildren ); for ( const auto& rChild : aChildren ) { HierarchyContentRef xChild = rChild; // Create new content identifier for the child... uno::Reference< ucb::XContentIdentifier > xOldChildId = xChild->getIdentifier(); OUString aOldChildURL = xOldChildId->getContentIdentifier(); OUString aNewChildURL = aOldChildURL.replaceAt( 0, aOldURL.getLength(), xNewId->getContentIdentifier() ); uno::Reference< ucb::XContentIdentifier > xNewChildId = new ::ucbhelper::ContentIdentifier( aNewChildURL ); if ( !xChild->exchangeIdentity( xNewChildId ) ) return false; } } return true; } } OSL_FAIL( "HierarchyContent::exchangeIdentity - " "Panic! Cannot exchange identity!" ); return false; } // static uno::Reference< sdbc::XRow > HierarchyContent::getPropertyValues( const uno::Reference< uno::XComponentContext >& rxContext, const uno::Sequence< beans::Property >& rProperties, const HierarchyContentProperties& rData, HierarchyContentProvider* pProvider, const OUString& rContentId ) { // Note: Empty sequence means "get values of all supported properties". rtl::Reference< ::ucbhelper::PropertyValueSet > xRow = new ::ucbhelper::PropertyValueSet( rxContext ); if ( rProperties.hasElements() ) { uno::Reference< beans::XPropertySet > xAdditionalPropSet; bool bTriedToGetAdditionalPropSet = false; for ( const beans::Property& rProp : rProperties ) { // Process Core properties. if ( rProp.Name == "ContentType" ) { xRow->appendString ( rProp, rData.getContentType() ); } else if ( rProp.Name == "Title" ) { xRow->appendString ( rProp, rData.getTitle() ); } else if ( rProp.Name == "IsDocument" ) { xRow->appendBoolean( rProp, rData.getIsDocument() ); } else if ( rProp.Name == "IsFolder" ) { xRow->appendBoolean( rProp, rData.getIsFolder() ); } else if ( rProp.Name == "CreatableContentsInfo" ) { xRow->appendObject( rProp, uno::Any( rData.getCreatableContentsInfo() ) ); } else if ( rProp.Name == "TargetURL" ) { // TargetURL is only supported by links. if ( rData.getIsDocument() ) xRow->appendString( rProp, rData.getTargetURL() ); else xRow->appendVoid( rProp ); } else { // Not a Core Property! Maybe it's an Additional Core Property?! if ( !bTriedToGetAdditionalPropSet && !xAdditionalPropSet.is() ) { xAdditionalPropSet = pProvider->getAdditionalPropertySet( rContentId, false ); bTriedToGetAdditionalPropSet = true; } if ( xAdditionalPropSet.is() ) { if ( !xRow->appendPropertySetValue( xAdditionalPropSet, rProp ) ) { // Append empty entry. xRow->appendVoid( rProp ); } } else { // Append empty entry. xRow->appendVoid( rProp ); } } } } else { // Append all Core Properties. xRow->appendString ( beans::Property( "ContentType", -1, cppu::UnoType::get(), beans::PropertyAttribute::BOUND | beans::PropertyAttribute::READONLY ), rData.getContentType() ); xRow->appendString ( beans::Property( "Title", -1, cppu::UnoType::get(), // @@@ Might actually be read-only! beans::PropertyAttribute::BOUND ), rData.getTitle() ); xRow->appendBoolean( beans::Property( "IsDocument", -1, cppu::UnoType::get(), beans::PropertyAttribute::BOUND | beans::PropertyAttribute::READONLY ), rData.getIsDocument() ); xRow->appendBoolean( beans::Property( "IsFolder", -1, cppu::UnoType::get(), beans::PropertyAttribute::BOUND | beans::PropertyAttribute::READONLY ), rData.getIsFolder() ); if ( rData.getIsDocument() ) xRow->appendString( beans::Property( "TargetURL", -1, cppu::UnoType::get(), // @@@ Might actually be read-only! beans::PropertyAttribute::BOUND ), rData.getTargetURL() ); xRow->appendObject( beans::Property( "CreatableContentsInfo", -1, cppu::UnoType>::get(), beans::PropertyAttribute::BOUND | beans::PropertyAttribute::READONLY ), uno::Any( rData.getCreatableContentsInfo() ) ); // Append all Additional Core Properties. uno::Reference< beans::XPropertySet > xSet = pProvider->getAdditionalPropertySet( rContentId, false ); xRow->appendPropertySet( xSet ); } return xRow; } uno::Reference< sdbc::XRow > HierarchyContent::getPropertyValues( const uno::Sequence< beans::Property >& rProperties ) { osl::Guard< osl::Mutex > aGuard( m_aMutex ); return getPropertyValues( m_xContext, rProperties, m_aProps, m_pProvider, m_xIdentifier->getContentIdentifier() ); } uno::Sequence< uno::Any > HierarchyContent::setPropertyValues( const uno::Sequence< beans::PropertyValue >& rValues, const uno::Reference< ucb::XCommandEnvironment > & xEnv ) { osl::ResettableGuard< osl::Mutex > aGuard( m_aMutex ); uno::Sequence< uno::Any > aRet( rValues.getLength() ); auto aRetRange = asNonConstRange(aRet); uno::Sequence< beans::PropertyChangeEvent > aChanges( rValues.getLength() ); sal_Int32 nChanged = 0; beans::PropertyChangeEvent aEvent; aEvent.Source = static_cast< cppu::OWeakObject * >( this ); aEvent.Further = false; // aEvent.PropertyName = aEvent.PropertyHandle = -1; // aEvent.OldValue = // aEvent.NewValue = const beans::PropertyValue* pValues = rValues.getConstArray(); sal_Int32 nCount = rValues.getLength(); uno::Reference< ucb::XPersistentPropertySet > xAdditionalPropSet; bool bTriedToGetAdditionalPropSet = false; bool bExchange = false; OUString aOldTitle; OUString aOldName; sal_Int32 nTitlePos = -1; for ( sal_Int32 n = 0; n < nCount; ++n ) { const beans::PropertyValue& rValue = pValues[ n ]; if ( rValue.Name == "ContentType" ) { // Read-only property! aRetRange[ n ] <<= lang::IllegalAccessException( "Property is read-only!", static_cast< cppu::OWeakObject * >( this ) ); } else if ( rValue.Name == "IsDocument" ) { // Read-only property! aRetRange[ n ] <<= lang::IllegalAccessException( "Property is read-only!", static_cast< cppu::OWeakObject * >( this ) ); } else if ( rValue.Name == "IsFolder" ) { // Read-only property! aRetRange[ n ] <<= lang::IllegalAccessException( "Property is read-only!", static_cast< cppu::OWeakObject * >( this ) ); } else if ( rValue.Name == "CreatableContentsInfo" ) { // Read-only property! aRetRange[ n ] <<= lang::IllegalAccessException( "Property is read-only!", static_cast< cppu::OWeakObject * >( this ) ); } else if ( rValue.Name == "Title" ) { if ( isReadOnly() ) { aRetRange[ n ] <<= lang::IllegalAccessException( "Property is read-only!", static_cast< cppu::OWeakObject * >( this ) ); } else { OUString aNewValue; if ( rValue.Value >>= aNewValue ) { // No empty titles! if ( !aNewValue.isEmpty() ) { if ( aNewValue != m_aProps.getTitle() ) { // modified title -> modified URL -> exchange ! if ( m_eState == PERSISTENT ) bExchange = true; aOldTitle = m_aProps.getTitle(); aOldName = m_aProps.getName(); m_aProps.setTitle( aNewValue ); m_aProps.setName( ::ucb_impl::urihelper::encodeSegment( aNewValue ) ); // property change event will be set later... // remember position within sequence of values // (for error handling). nTitlePos = n; } } else { aRetRange[ n ] <<= lang::IllegalArgumentException( "Empty title not allowed!", static_cast< cppu::OWeakObject * >( this ), -1 ); } } else { aRetRange[ n ] <<= beans::IllegalTypeException( "Property value has wrong type!", static_cast< cppu::OWeakObject * >( this ) ); } } } else if ( rValue.Name == "TargetURL" ) { if ( isReadOnly() ) { aRetRange[ n ] <<= lang::IllegalAccessException( "Property is read-only!", static_cast< cppu::OWeakObject * >( this ) ); } else { // TargetURL is only supported by links. if ( m_eKind == LINK ) { OUString aNewValue; if ( rValue.Value >>= aNewValue ) { // No empty target URL's! if ( !aNewValue.isEmpty() ) { if ( aNewValue != m_aProps.getTargetURL() ) { aEvent.PropertyName = rValue.Name; aEvent.OldValue <<= m_aProps.getTargetURL(); aEvent.NewValue <<= aNewValue; aChanges.getArray()[ nChanged ] = aEvent; m_aProps.setTargetURL( aNewValue ); nChanged++; } } else { aRetRange[ n ] <<= lang::IllegalArgumentException( "Empty target URL not allowed!", static_cast< cppu::OWeakObject * >( this ), -1 ); } } else { aRetRange[ n ] <<= beans::IllegalTypeException( "Property value has wrong type!", static_cast< cppu::OWeakObject * >( this ) ); } } else { aRetRange[ n ] <<= beans::UnknownPropertyException( "TargetURL only supported by links!", static_cast< cppu::OWeakObject * >( this ) ); } } } else { // Not a Core Property! Maybe it's an Additional Core Property?! if ( !bTriedToGetAdditionalPropSet && !xAdditionalPropSet.is() ) { xAdditionalPropSet = getAdditionalPropertySet( false ); bTriedToGetAdditionalPropSet = true; } if ( xAdditionalPropSet.is() ) { try { uno::Any aOldValue = xAdditionalPropSet->getPropertyValue( rValue.Name ); if ( aOldValue != rValue.Value ) { xAdditionalPropSet->setPropertyValue( rValue.Name, rValue.Value ); aEvent.PropertyName = rValue.Name; aEvent.OldValue = aOldValue; aEvent.NewValue = rValue.Value; aChanges.getArray()[ nChanged ] = aEvent; nChanged++; } } catch ( beans::UnknownPropertyException const & e ) { aRetRange[ n ] <<= e; } catch ( lang::WrappedTargetException const & e ) { aRetRange[ n ] <<= e; } catch ( beans::PropertyVetoException const & e ) { aRetRange[ n ] <<= e; } catch ( lang::IllegalArgumentException const & e ) { aRetRange[ n ] <<= e; } } else { aRetRange[ n ] <<= uno::Exception( "No property set for storing the value!", static_cast< cppu::OWeakObject * >( this ) ); } } } if ( bExchange ) { uno::Reference< ucb::XContentIdentifier > xOldId = m_xIdentifier; uno::Reference< ucb::XContentIdentifier > xNewId = makeNewIdentifier( m_aProps.getTitle() ); aGuard.clear(); if ( exchangeIdentity( xNewId ) ) { // Adapt persistent data. renameData( xOldId, xNewId ); // Adapt Additional Core Properties. renameAdditionalPropertySet( xOldId->getContentIdentifier(), xNewId->getContentIdentifier() ); } else { // Roll-back. m_aProps.setTitle( aOldTitle ); m_aProps.setName ( aOldName ); aOldTitle.clear(); aOldName.clear(); // Set error . aRetRange[ nTitlePos ] <<= uno::Exception( "Exchange failed!", static_cast< cppu::OWeakObject * >( this ) ); } aGuard.reset(); } if ( !aOldTitle.isEmpty() ) { aEvent.PropertyName = "Title"; aEvent.OldValue <<= aOldTitle; aEvent.NewValue <<= m_aProps.getTitle(); aChanges.getArray()[ nChanged ] = aEvent; nChanged++; } if ( nChanged > 0 ) { // Save changes, if content was already made persistent. if ( !bExchange && ( m_eState == PERSISTENT ) ) { if ( !storeData() ) { uno::Sequence aArgs(comphelper::InitAnyPropertySequence( { {"Uri", uno::Any(m_xIdentifier->getContentIdentifier())} })); ucbhelper::cancelCommandExecution( ucb::IOErrorCode_CANT_WRITE, aArgs, xEnv, "Cannot store persistent data!", this ); // Unreachable } } aChanges.realloc( nChanged ); aGuard.clear(); notifyPropertiesChange( aChanges ); } return aRet; } void HierarchyContent::insert( sal_Int32 nNameClashResolve, const uno::Reference< ucb::XCommandEnvironment > & xEnv ) { osl::ClearableGuard< osl::Mutex > aGuard( m_aMutex ); // Am I the root folder? if ( m_eKind == ROOT ) { ucbhelper::cancelCommandExecution( uno::Any( ucb::UnsupportedCommandException( "Not supported by root folder!", static_cast< cppu::OWeakObject * >( this ) ) ), xEnv ); // Unreachable } // Check, if all required properties were set. if ( m_aProps.getTitle().isEmpty() ) { uno::Sequence aProps { "Title" }; ucbhelper::cancelCommandExecution( uno::Any( ucb::MissingPropertiesException( OUString(), static_cast< cppu::OWeakObject * >( this ), aProps ) ), xEnv ); // Unreachable } // Assemble new content identifier... uno::Reference< ucb::XContentIdentifier > xId = makeNewIdentifier( m_aProps.getTitle() ); // Handle possible name clash... switch ( nNameClashResolve ) { // fail. case ucb::NameClash::ERROR: if ( hasData( xId ) ) { ucbhelper::cancelCommandExecution( uno::Any( ucb::NameClashException( OUString(), static_cast< cppu::OWeakObject * >( this ), task::InteractionClassification_ERROR, m_aProps.getTitle() ) ), xEnv ); // Unreachable } break; // replace existing object. case ucb::NameClash::OVERWRITE: break; // "invent" a new valid title. case ucb::NameClash::RENAME: if ( hasData( xId ) ) { sal_Int32 nTry = 0; do { OUString aNewId = xId->getContentIdentifier() + "_"; aNewId += OUString::number( ++nTry ); xId = new ::ucbhelper::ContentIdentifier( aNewId ); } while ( hasData( xId ) && ( nTry < 1000 ) ); if ( nTry == 1000 ) { ucbhelper::cancelCommandExecution( uno::Any( ucb::UnsupportedNameClashException( "Unable to resolve name clash!", static_cast< cppu::OWeakObject * >( this ), nNameClashResolve ) ), xEnv ); // Unreachable } else { OUString aNewTitle( m_aProps.getTitle() + "_" + OUString::number( nTry ) ); m_aProps.setTitle( aNewTitle ); } } break; case ucb::NameClash::KEEP: // deprecated case ucb::NameClash::ASK: default: if ( hasData( xId ) ) { ucbhelper::cancelCommandExecution( uno::Any( ucb::UnsupportedNameClashException( OUString(), static_cast< cppu::OWeakObject * >( this ), nNameClashResolve ) ), xEnv ); // Unreachable } break; } // Identifier changed? bool bNewId = ( xId->getContentIdentifier() != m_xIdentifier->getContentIdentifier() ); m_xIdentifier = xId; if ( !storeData() ) { uno::Sequence aArgs(comphelper::InitAnyPropertySequence( { {"Uri", uno::Any(m_xIdentifier->getContentIdentifier())} })); ucbhelper::cancelCommandExecution( ucb::IOErrorCode_CANT_WRITE, aArgs, xEnv, "Cannot store persistent data!", this ); // Unreachable } m_eState = PERSISTENT; if ( bNewId ) { aGuard.clear(); inserted(); } } void HierarchyContent::destroy( bool bDeletePhysical, const uno::Reference< ucb::XCommandEnvironment > & xEnv ) { // @@@ take care about bDeletePhysical -> trashcan support osl::ClearableGuard< osl::Mutex > aGuard( m_aMutex ); uno::Reference< ucb::XContent > xThis = this; // Persistent? if ( m_eState != PERSISTENT ) { ucbhelper::cancelCommandExecution( uno::Any( ucb::UnsupportedCommandException( "Not persistent!", static_cast< cppu::OWeakObject * >( this ) ) ), xEnv ); // Unreachable } // Am I the root folder? if ( m_eKind == ROOT ) { ucbhelper::cancelCommandExecution( uno::Any( ucb::UnsupportedCommandException( "Not supported by root folder!", static_cast< cppu::OWeakObject * >( this ) ) ), xEnv ); // Unreachable } m_eState = DEAD; aGuard.clear(); deleted(); if ( m_eKind == FOLDER ) { // Process instantiated children... HierarchyContentRefVector aChildren; queryChildren( aChildren ); for ( auto & child : aChildren) { child->destroy( bDeletePhysical, xEnv ); } } } void HierarchyContent::transfer( const ucb::TransferInfo& rInfo, const uno::Reference< ucb::XCommandEnvironment > & xEnv ) { osl::ClearableGuard< osl::Mutex > aGuard( m_aMutex ); // Persistent? if ( m_eState != PERSISTENT ) { ucbhelper::cancelCommandExecution( uno::Any( ucb::UnsupportedCommandException( "Not persistent!", static_cast< cppu::OWeakObject * >( this ) ) ), xEnv ); // Unreachable } // Is source a hierarchy content? if ( !rInfo.SourceURL.startsWith( HIERARCHY_URL_SCHEME ":/" ) ) { ucbhelper::cancelCommandExecution( uno::Any( ucb::InteractiveBadTransferURLException( OUString(), static_cast< cppu::OWeakObject * >( this ) ) ), xEnv ); // Unreachable } // Is source not a parent of me / not me? OUString aId = m_xIdentifier->getContentIdentifier(); sal_Int32 nPos = aId.lastIndexOf( '/' ); if ( nPos != ( aId.getLength() - 1 ) ) { // No trailing slash found. Append. aId += "/"; } if ( rInfo.SourceURL.getLength() <= aId.getLength() ) { if ( aId.startsWith( rInfo.SourceURL ) ) { uno::Sequence aArgs(comphelper::InitAnyPropertySequence( { {"Uri", uno::Any(rInfo.SourceURL)} })); ucbhelper::cancelCommandExecution( ucb::IOErrorCode_RECURSIVE, aArgs, xEnv, "Target is equal to or is a child of source!", this ); // Unreachable } } // 0) Obtain content object for source. uno::Reference< ucb::XContentIdentifier > xId = new ::ucbhelper::ContentIdentifier( rInfo.SourceURL ); // Note: The static cast is okay here, because its sure that // m_xProvider is always the HierarchyContentProvider. rtl::Reference< HierarchyContent > xSource; try { xSource = static_cast< HierarchyContent * >( m_xProvider->queryContent( xId ).get() ); } catch ( ucb::IllegalIdentifierException const & ) { // queryContent } if ( !xSource.is() ) { uno::Sequence aArgs(comphelper::InitAnyPropertySequence( { {"Uri", uno::Any(xId->getContentIdentifier())} })); ucbhelper::cancelCommandExecution( ucb::IOErrorCode_CANT_READ, aArgs, xEnv, "Cannot instantiate source object!", this ); // Unreachable } // 1) Create new child content. OUString aType = xSource->isFolder() ? OUString( HIERARCHY_FOLDER_CONTENT_TYPE ) : OUString( HIERARCHY_LINK_CONTENT_TYPE ); ucb::ContentInfo aContentInfo; aContentInfo.Type = aType; aContentInfo.Attributes = 0; // Note: The static cast is okay here, because its sure that // createNewContent always creates a HierarchyContent. rtl::Reference< HierarchyContent > xTarget = static_cast< HierarchyContent * >( createNewContent( aContentInfo ).get() ); if ( !xTarget.is() ) { uno::Sequence aArgs(comphelper::InitAnyPropertySequence( { {"Folder", uno::Any(aId)} })); ucbhelper::cancelCommandExecution( ucb::IOErrorCode_CANT_CREATE, aArgs, xEnv, "XContentCreator::createNewContent failed!", this ); // Unreachable } // 2) Copy data from source content to child content. uno::Sequence< beans::Property > aSourceProps = xSource->getPropertySetInfo( xEnv )->getProperties(); sal_Int32 nCount = aSourceProps.getLength(); if ( nCount ) { bool bHadTitle = rInfo.NewTitle.isEmpty(); // Get all source values. uno::Reference< sdbc::XRow > xRow = xSource->getPropertyValues( aSourceProps ); uno::Sequence< beans::PropertyValue > aValues( nCount ); beans::PropertyValue* pValues = aValues.getArray(); const beans::Property* pProps = aSourceProps.getConstArray(); for ( sal_Int32 n = 0; n < nCount; ++n ) { const beans::Property& rProp = pProps[ n ]; beans::PropertyValue& rValue = pValues[ n ]; rValue.Name = rProp.Name; rValue.Handle = rProp.Handle; if ( !bHadTitle && rProp.Name == "Title" ) { // Set new title instead of original. bHadTitle = true; rValue.Value <<= rInfo.NewTitle; } else rValue.Value = xRow->getObject( n + 1, uno::Reference< container::XNameAccess >() ); rValue.State = beans::PropertyState_DIRECT_VALUE; if ( rProp.Attributes & beans::PropertyAttribute::REMOVABLE ) { // Add Additional Core Property. try { xTarget->addProperty( rProp.Name, rProp.Attributes, rValue.Value ); } catch ( beans::PropertyExistException const & ) { } catch ( beans::IllegalTypeException const & ) { } catch ( lang::IllegalArgumentException const & ) { } } } // Set target values. xTarget->setPropertyValues( aValues, xEnv ); } // 3) Commit (insert) child. xTarget->insert( rInfo.NameClash, xEnv ); // 4) Transfer (copy) children of source. if ( xSource->isFolder() ) { HierarchyEntry aFolder( m_xContext, m_pProvider, xId->getContentIdentifier() ); HierarchyEntry::iterator it; while ( aFolder.next( it ) ) { const HierarchyEntryData& rResult = *it; OUString aChildId = xId->getContentIdentifier(); if ( ( aChildId.lastIndexOf( '/' ) + 1 ) != aChildId.getLength() ) aChildId += "/"; aChildId += rResult.getName(); ucb::TransferInfo aInfo; aInfo.MoveData = false; aInfo.NewTitle.clear(); aInfo.SourceURL = aChildId; aInfo.NameClash = rInfo.NameClash; // Transfer child to target. xTarget->transfer( aInfo, xEnv ); } } // 5) Destroy source ( when moving only ) . if ( !rInfo.MoveData ) return; xSource->destroy( true, xEnv ); // Remove all persistent data of source and its children. if ( !xSource->removeData() ) { uno::Sequence aArgs(comphelper::InitAnyPropertySequence( { {"Uri", uno::Any(xSource->m_xIdentifier->getContentIdentifier())} })); ucbhelper::cancelCommandExecution( ucb::IOErrorCode_CANT_WRITE, aArgs, xEnv, "Cannot remove persistent data of source object!", this ); // Unreachable } // Remove own and all children's Additional Core Properties. xSource->removeAdditionalPropertySet(); } // HierarchyContentProperties Implementation. uno::Sequence< ucb::ContentInfo > HierarchyContentProperties::getCreatableContentsInfo() const { if ( getIsFolder() ) { uno::Sequence< ucb::ContentInfo > aSeq( 2 ); // Folder. aSeq.getArray()[ 0 ].Type = HIERARCHY_FOLDER_CONTENT_TYPE; aSeq.getArray()[ 0 ].Attributes = ucb::ContentInfoAttribute::KIND_FOLDER; uno::Sequence< beans::Property > aFolderProps( 1 ); aFolderProps.getArray()[ 0 ] = beans::Property( "Title", -1, cppu::UnoType::get(), beans::PropertyAttribute::BOUND ); aSeq.getArray()[ 0 ].Properties = aFolderProps; // Link. aSeq.getArray()[ 1 ].Type = HIERARCHY_LINK_CONTENT_TYPE; aSeq.getArray()[ 1 ].Attributes = ucb::ContentInfoAttribute::KIND_LINK; uno::Sequence< beans::Property > aLinkProps( 2 ); aLinkProps.getArray()[ 0 ] = beans::Property( "Title", -1, cppu::UnoType::get(), beans::PropertyAttribute::BOUND ); aLinkProps.getArray()[ 1 ] = beans::Property( "TargetURL", -1, cppu::UnoType::get(), beans::PropertyAttribute::BOUND ); aSeq.getArray()[ 1 ].Properties = aLinkProps; return aSeq; } else { return uno::Sequence< ucb::ContentInfo >( 0 ); } } /* vim:set shiftwidth=4 softtabstop=4 expandtab: */