///////////////////////////////////////////////////////////////////////////////// // // Copyright (C) 2019- Equinor ASA // // ResInsight is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. // // ResInsight is distributed in the hope that it will be useful, but WITHOUT ANY // WARRANTY; without even the implied warranty of MERCHANTABILITY or // FITNESS FOR A PARTICULAR PURPOSE. // // See the GNU General Public License at // for more details. // ////////////////////////////////////////////////////////////////////////////////// #include "RiaGrpcPdmObjectService.h" #include "RiaApplication.h" #include "RiaGrpcCallbacks.h" #include "RiaGrpcHelper.h" #include "RiaLogging.h" #include "RiaOpenTelemetryManager.h" #include "RimEclipseResultDefinition.h" #include "RimProject.h" #include "cafPdmAbstractFieldScriptingCapability.h" #include "cafPdmObject.h" #include "cafPdmObjectMethod.h" #include "cafPdmObjectScriptingCapability.h" #include "cafPdmObjectScriptingCapabilityRegister.h" #include "cafPdmProxyValueField.h" using namespace rips; template struct DataHolder : public AbstractDataHolder { DataHolder( const DataType& data ) : data( data ) { } size_t dataCount() const override { return data.size(); } size_t dataSizeOf() const override { return sizeof( typename DataType::value_type ); } void reserveReplyStorage( rips::PdmObjectGetterReply* reply ) const override; void addValueToReply( size_t valueIndex, rips::PdmObjectGetterReply* reply ) const override; size_t getValuesFromChunk( size_t startIndex, const rips::PdmObjectSetterChunk* chunk ) override; void applyValuesToProxyField( caf::PdmProxyFieldHandle* proxyField ) override; DataType data; }; template <> void DataHolder>::reserveReplyStorage( rips::PdmObjectGetterReply* reply ) const { reply->mutable_ints()->mutable_data()->Reserve( data.size() ); } template <> void DataHolder>::addValueToReply( size_t valueIndex, rips::PdmObjectGetterReply* reply ) const { reply->mutable_ints()->add_data( data[valueIndex] ); } template <> size_t DataHolder>::getValuesFromChunk( size_t startIndex, const rips::PdmObjectSetterChunk* chunk ) { size_t chunkSize = chunk->ints().data_size(); size_t currentIndex = startIndex; size_t chunkIndex = 0u; for ( ; chunkIndex < chunkSize && currentIndex < data.size(); ++currentIndex, ++chunkIndex ) { data[currentIndex] = chunk->ints().data()[chunkIndex]; } return chunkSize; } template <> void DataHolder>::applyValuesToProxyField( caf::PdmProxyFieldHandle* proxyField ) { auto proxyValueField = dynamic_cast>*>( proxyField ); CAF_ASSERT( proxyValueField ); if ( proxyValueField ) { proxyValueField->setValue( data ); } } template <> void DataHolder>::reserveReplyStorage( rips::PdmObjectGetterReply* reply ) const { reply->mutable_doubles()->mutable_data()->Reserve( data.size() ); } template <> void DataHolder>::addValueToReply( size_t valueIndex, rips::PdmObjectGetterReply* reply ) const { reply->mutable_doubles()->add_data( data[valueIndex] ); } template <> size_t DataHolder>::getValuesFromChunk( size_t startIndex, const rips::PdmObjectSetterChunk* chunk ) { size_t chunkSize = chunk->doubles().data_size(); size_t currentIndex = startIndex; size_t chunkIndex = 0u; for ( ; chunkIndex < chunkSize && currentIndex < data.size(); ++currentIndex, ++chunkIndex ) { data[currentIndex] = chunk->doubles().data()[chunkIndex]; } return chunkSize; } template <> void DataHolder>::applyValuesToProxyField( caf::PdmProxyFieldHandle* proxyField ) { auto proxyValueField = dynamic_cast>*>( proxyField ); CAF_ASSERT( proxyValueField ); if ( proxyValueField ) { proxyValueField->setValue( data ); } } template <> void DataHolder>::reserveReplyStorage( rips::PdmObjectGetterReply* reply ) const { reply->mutable_strings()->mutable_data()->Reserve( data.size() ); } template <> void DataHolder>::addValueToReply( size_t valueIndex, rips::PdmObjectGetterReply* reply ) const { reply->mutable_strings()->add_data( data[valueIndex].toStdString() ); } template <> size_t DataHolder>::getValuesFromChunk( size_t startIndex, const rips::PdmObjectSetterChunk* chunk ) { size_t chunkSize = chunk->strings().data_size(); size_t currentIndex = startIndex; size_t chunkIndex = 0u; for ( ; chunkIndex < chunkSize && currentIndex < data.size(); ++currentIndex, ++chunkIndex ) { data[currentIndex] = QString::fromStdString( chunk->strings().data()[chunkIndex] ); } return chunkSize; } template <> void DataHolder>::applyValuesToProxyField( caf::PdmProxyFieldHandle* proxyField ) { auto proxyValueField = dynamic_cast>*>( proxyField ); CAF_ASSERT( proxyValueField ); if ( proxyValueField ) { proxyValueField->setValue( data ); } } //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- RiaPdmObjectMethodStateHandler::RiaPdmObjectMethodStateHandler( bool clientToServerStreamer ) : m_fieldOwner( nullptr ) , m_proxyField( nullptr ) , m_currentDataIndex( 0u ) , m_clientToServerStreamer( clientToServerStreamer ) { } //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- Status RiaPdmObjectMethodStateHandler::init( const rips::PdmObjectGetterRequest* request ) { CAF_ASSERT( !m_clientToServerStreamer ); m_fieldOwner = RiaGrpcHelper::findCafObjectFromRipsObject( request->object() ); QString fieldName = QString::fromStdString( request->method() ); std::vector fields = m_fieldOwner->fields(); for ( auto field : fields ) { auto scriptability = field->capability(); if ( scriptability && scriptability->scriptFieldName() == fieldName ) { caf::PdmProxyFieldHandle* proxyField = dynamic_cast( field ); if ( proxyField ) { m_proxyField = proxyField; if ( dynamic_cast>*>( field ) ) { auto dataField = dynamic_cast>*>( field ); m_dataHolder.reset( new DataHolder>( dataField->value() ) ); return grpc::Status::OK; } else if ( dynamic_cast>*>( field ) ) { auto dataField = dynamic_cast>*>( field ); m_dataHolder.reset( new DataHolder>( dataField->value() ) ); return grpc::Status::OK; } else if ( dynamic_cast>*>( field ) ) { auto dataField = dynamic_cast>*>( field ); m_dataHolder.reset( new DataHolder>( dataField->value() ) ); return grpc::Status::OK; } else { CAF_ASSERT( false && "The proxy field data type is not yet supported for streaming fields" ); } } } } return grpc::Status( grpc::NOT_FOUND, "Proxy field not found" ); } //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- Status RiaPdmObjectMethodStateHandler::init( const rips::PdmObjectSetterChunk* chunk ) { CAF_ASSERT( m_clientToServerStreamer ); CAF_ASSERT( chunk->has_set_request() ); auto setRequest = chunk->set_request(); auto methodRequest = setRequest.request(); m_fieldOwner = RiaGrpcHelper::findCafObjectFromRipsObject( methodRequest.object() ); QString fieldName = QString::fromStdString( methodRequest.method() ); int valueCount = setRequest.data_count(); std::vector fields = m_fieldOwner->fields(); for ( auto field : fields ) { auto scriptability = field->capability(); if ( scriptability && scriptability->scriptFieldName() == fieldName ) { auto* proxyField = dynamic_cast( field ); if ( proxyField ) { m_proxyField = proxyField; if ( dynamic_cast>*>( field ) ) { m_dataHolder = std::make_unique>>( std::vector( valueCount ) ); return grpc::Status::OK; } if ( dynamic_cast>*>( field ) ) { m_dataHolder = std::make_unique>>( std::vector( valueCount ) ); return grpc::Status::OK; } if ( dynamic_cast>*>( field ) ) { m_dataHolder = std::make_unique>>( std::vector( valueCount ) ); return grpc::Status::OK; } CAF_ASSERT( false && "The proxy field data type is not yet supported for streaming fields" ); } } } return grpc::Status( grpc::NOT_FOUND, "Proxy field not found" ); } //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- Status RiaPdmObjectMethodStateHandler::assignReply( rips::PdmObjectGetterReply* reply ) { CAF_ASSERT( m_dataHolder ); const size_t packageSize = RiaGrpcHelper::numberOfDataUnitsInPackage( m_dataHolder->dataSizeOf() ); size_t indexInPackage = 0u; m_dataHolder->reserveReplyStorage( reply ); for ( ; indexInPackage < packageSize && m_currentDataIndex < m_dataHolder->dataCount(); ++indexInPackage ) { m_dataHolder->addValueToReply( m_currentDataIndex, reply ); m_currentDataIndex++; } if ( indexInPackage > 0u ) { return grpc::Status::OK; } return grpc::Status( grpc::OUT_OF_RANGE, "We've reached the end. This is not an error but means transmission is finished" ); } //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- Status RiaPdmObjectMethodStateHandler::receiveRequest( const rips::PdmObjectSetterChunk* chunk, rips::ClientToServerStreamReply* reply ) { size_t valuesWritten = m_dataHolder->getValuesFromChunk( m_currentDataIndex, chunk ); m_currentDataIndex += valuesWritten; if ( m_currentDataIndex > totalValueCount() ) { return grpc::Status( grpc::OUT_OF_RANGE, "Attempting to write out of bounds" ); } reply->set_accepted_value_count( static_cast( m_currentDataIndex ) ); return grpc::Status::OK; } //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- size_t RiaPdmObjectMethodStateHandler::streamedValueCount() const { return m_currentDataIndex; } //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- size_t RiaPdmObjectMethodStateHandler::totalValueCount() const { return m_dataHolder->dataCount(); } //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- void RiaPdmObjectMethodStateHandler::finish() { if ( m_proxyField ) { QVariant before = m_proxyField->toQVariant(); m_dataHolder->applyValuesToProxyField( m_proxyField ); QVariant after = m_proxyField->toQVariant(); m_fieldOwner->fieldChangedByUi( m_proxyField, before, after ); } } //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- grpc::Status RiaGrpcPdmObjectService::GetAncestorPdmObject( grpc::ServerContext* context, const rips::PdmParentObjectRequest* request, rips::PdmObject* reply ) { RimProject* project = RimProject::current(); std::vector objectsOfCurrentClass; QString scriptClassName = QString::fromStdString( request->object().class_keyword() ); QString classKeyword = caf::PdmObjectScriptingCapabilityRegister::classKeywordFromScriptClassName( scriptClassName ); project->descendantsIncludingThisFromClassKeyword( classKeyword, objectsOfCurrentClass ); caf::PdmObject* matchingObject = nullptr; for ( caf::PdmObject* testObject : objectsOfCurrentClass ) { if ( reinterpret_cast( testObject ) == request->object().address() ) { matchingObject = testObject; } } if ( matchingObject ) { caf::PdmObject* parentObject = nullptr; QString ancestorScriptName = QString::fromStdString( request->parent_keyword() ); QString ancestorClassKeyword = caf::PdmObjectScriptingCapabilityRegister::classKeywordFromScriptClassName( ancestorScriptName ); matchingObject->firstAncestorOrThisFromClassKeyword( ancestorClassKeyword, parentObject ); if ( parentObject ) { copyPdmObjectFromCafToRips( parentObject, reply ); return grpc::Status::OK; } } return grpc::Status( grpc::NOT_FOUND, "Parent PdmObject not found" ); } //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- grpc::Status RiaGrpcPdmObjectService::GetDescendantPdmObjects( grpc::ServerContext* context, const rips::PdmDescendantObjectRequest* request, rips::PdmObjectArray* reply ) { auto matchingObject = RiaGrpcHelper::findCafObjectFromRipsObject( request->object() ); if ( matchingObject ) { std::vector childObjects; QString childClassKeyword = caf::PdmObjectScriptingCapabilityRegister::classKeywordFromScriptClassName( QString::fromStdString( request->child_keyword() ) ); matchingObject->descendantsIncludingThisFromClassKeyword( childClassKeyword, childObjects ); for ( auto pdmChild : childObjects ) { rips::PdmObject* ripsChild = reply->add_objects(); copyPdmObjectFromCafToRips( pdmChild, ripsChild ); } return grpc::Status::OK; } return grpc::Status( grpc::NOT_FOUND, "Current PdmObject not found" ); } //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- grpc::Status RiaGrpcPdmObjectService::GetChildPdmObjects( grpc::ServerContext* context, const rips::PdmChildObjectRequest* request, rips::PdmObjectArray* reply ) { auto matchingObject = RiaGrpcHelper::findCafObjectFromRipsObject( request->object() ); if ( matchingObject ) { QString fieldName = QString::fromStdString( request->child_field() ); std::vector fields = matchingObject->fields(); for ( auto field : fields ) { auto scriptability = field->capability(); if ( scriptability && scriptability->scriptFieldName() == fieldName ) { std::vector childObjects = field->children(); for ( auto pdmChild : childObjects ) { rips::PdmObject* ripsChild = reply->add_objects(); copyPdmObjectFromCafToRips( static_cast( pdmChild ), ripsChild ); } return grpc::Status::OK; } } return grpc::Status( grpc::NOT_FOUND, "Child field not found" ); } return grpc::Status( grpc::NOT_FOUND, "Current PdmObject not found" ); } //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- grpc::Status RiaGrpcPdmObjectService::UpdateExistingPdmObject( grpc::ServerContext* context, const rips::PdmObject* request, rips::Empty* response ) { auto matchingObject = RiaGrpcHelper::findCafObjectFromRipsObject( *request ); if ( !matchingObject ) { return grpc::Status( grpc::NOT_FOUND, "PdmObject not found" ); } // Validate and copy fields from request auto result = copyPdmObjectFromRipsToCaf( request, matchingObject ); if ( !result ) { // Validation failed - return error to Python return grpc::Status( grpc::INVALID_ARGUMENT, result.error().toStdString() ); } // Validation succeeded - update UI and views RimEclipseResultDefinition* resultDefinition = dynamic_cast( matchingObject ); // TODO: Make this more general. Perhaps we need an interface method for updating UI fields if ( resultDefinition ) { resultDefinition->updateUiFieldsFromActiveResult(); resultDefinition->loadResult(); } matchingObject->updateAllRequiredEditors(); RimProject::current()->scheduleCreateDisplayModelAndRedrawAllViews(); return grpc::Status::OK; } //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- grpc::Status RiaGrpcPdmObjectService::DeleteExistingPdmObject( grpc::ServerContext* context, const rips::PdmObject* request, rips::Empty* response ) { auto matchingObject = RiaGrpcHelper::findCafObjectFromRipsObject( *request ); if ( matchingObject && matchingObject->parentField() ) { auto parentField = matchingObject->parentField(); parentField->removeChild( matchingObject ); auto obj = parentField->ownerObject(); if ( obj && obj->uiCapability() ) { std::vector referringObjects; obj->onChildDeleted( nullptr, referringObjects ); obj->uiCapability()->updateAllRequiredEditors(); } delete matchingObject; return grpc::Status::OK; } return grpc::Status( grpc::NOT_FOUND, "PdmObject not found" ); } //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- grpc::Status RiaGrpcPdmObjectService::CreateChildPdmObject( grpc::ServerContext* context, const rips::CreatePdmChildObjectRequest* request, rips::PdmObject* reply ) { auto matchingObject = RiaGrpcHelper::findCafObjectFromRipsObject( request->object() ); if ( matchingObject ) { CAF_ASSERT( request ); QString keywordClassToCreate = QString::fromStdString( request->class_keyword() ); QString fieldKeyword = QString::fromStdString( request->child_field() ); caf::PdmObjectHandle* pdmObjectHandle = emplaceChildField( matchingObject, fieldKeyword, keywordClassToCreate ); if ( pdmObjectHandle ) { copyPdmObjectFromCafToRips( pdmObjectHandle, reply ); matchingObject->uiCapability()->updateConnectedEditors(); return grpc::Status::OK; } return grpc::Status( grpc::NOT_FOUND, "Could not create PdmObject" ); } return grpc::Status( grpc::NOT_FOUND, "Could not find PdmObject" ); } //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- grpc::Status RiaGrpcPdmObjectService::CallPdmObjectGetter( grpc::ServerContext* context, const rips::PdmObjectGetterRequest* request, rips::PdmObjectGetterReply* reply, RiaPdmObjectMethodStateHandler* stateHandler ) { return stateHandler->assignReply( reply ); } //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- grpc::Status RiaGrpcPdmObjectService::CallPdmObjectSetter( grpc::ServerContext* context, const rips::PdmObjectSetterChunk* chunk, rips::ClientToServerStreamReply* reply, RiaPdmObjectMethodStateHandler* stateHandler ) { return stateHandler->receiveRequest( chunk, reply ); } //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- grpc::Status RiaGrpcPdmObjectService::CallPdmObjectMethod( grpc::ServerContext* context, const rips::PdmObjectMethodRequest* request, rips::PdmObject* reply ) { auto matchingObject = RiaGrpcHelper::findCafObjectFromRipsObject( request->object() ); if ( matchingObject ) { QString methodKeyword = QString::fromStdString( request->method() ); // Prepare telemetry attributes std::map telemetryAttributes; QString objectClassName = QString::fromStdString( request->object().class_keyword() ); telemetryAttributes["pdm.class"] = objectClassName.toStdString(); telemetryAttributes["pdm.method"] = methodKeyword.toStdString(); std::shared_ptr method = caf::PdmObjectMethodFactory::instance()->createMethod( matchingObject, methodKeyword ); if ( method ) { if ( auto result = copyPdmObjectFromRipsToCaf( &( request->params() ), method.get() ); !result ) { telemetryAttributes["pdm.status"] = "failed"; telemetryAttributes["pdm.error"] = result.error().toStdString(); RiaOpenTelemetryManager::instance().reportEventAsync( "grpc.pdm_method_call", telemetryAttributes ); RiaLogging::error( QString( "Method '%1' failed. Error: %2" ).arg( methodKeyword ).arg( result.error() ).toStdString() ); return grpc::Status( grpc::INVALID_ARGUMENT, result.error().toStdString() ); } // clang-tidy-19 has a bug that did the following: // std::expected result = method->execute(); // was replaced with // std::unexpected result = method->execute(); // Using auto works around the problem auto result = method->execute(); if ( !result.has_value() ) { telemetryAttributes["pdm.status"] = "failed"; telemetryAttributes["pdm.error"] = result.error().toStdString(); RiaOpenTelemetryManager::instance().reportEventAsync( "grpc.pdm_method_call", telemetryAttributes ); RiaLogging::error( QString( "Method '%1' failed. Error: %2" ).arg( methodKeyword ).arg( result.error() ).toStdString() ); return grpc::Status( grpc::NOT_FOUND, result.error().toStdString() ); } else { caf::PdmObjectHandle* object = result.value(); if ( object ) { telemetryAttributes["pdm.status"] = "success"; telemetryAttributes["pdm.result_class"] = object->classKeywordStatic().toStdString(); RiaOpenTelemetryManager::instance().reportEventAsync( "grpc.pdm_method_call", telemetryAttributes ); copyPdmObjectFromCafToRips( object, reply ); if ( !method->resultIsPersistent() ) { delete object; } return grpc::Status::OK; } if ( method->isNullptrValidResult() ) { telemetryAttributes["pdm.status"] = "success"; telemetryAttributes["pdm.result_class"] = "null"; RiaOpenTelemetryManager::instance().reportEventAsync( "grpc.pdm_method_call", telemetryAttributes ); return grpc::Status::OK; } telemetryAttributes["pdm.status"] = "failed"; telemetryAttributes["pdm.error"] = "No result returned from Method"; RiaOpenTelemetryManager::instance().reportEventAsync( "grpc.pdm_method_call", telemetryAttributes ); return grpc::Status( grpc::NOT_FOUND, QString( "Method '%1' returned no result" ).arg( methodKeyword ).toStdString() ); } } telemetryAttributes["pdm.status"] = "failed"; telemetryAttributes["pdm.error"] = "Could not find Method"; RiaOpenTelemetryManager::instance().reportEventAsync( "grpc.pdm_method_call", telemetryAttributes ); return grpc::Status( grpc::NOT_FOUND, "Could not find Method" ); } std::map telemetryAttributes; telemetryAttributes["pdm.status"] = "failed"; telemetryAttributes["pdm.error"] = "Could not find PdmObject"; RiaOpenTelemetryManager::instance().reportEventAsync( "grpc.pdm_method_call", telemetryAttributes ); return grpc::Status( grpc::NOT_FOUND, "Could not find PdmObject" ); } //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- std::vector RiaGrpcPdmObjectService::createCallbacks() { using Self = RiaGrpcPdmObjectService; return { new RiaGrpcUnaryCallback( this, &Self::GetAncestorPdmObject, &Self::RequestGetAncestorPdmObject ), new RiaGrpcUnaryCallback( this, &Self::GetDescendantPdmObjects, &Self::RequestGetDescendantPdmObjects ), new RiaGrpcUnaryCallback( this, &Self::GetChildPdmObjects, &Self::RequestGetChildPdmObjects ), new RiaGrpcUnaryCallback( this, &Self::UpdateExistingPdmObject, &Self::RequestUpdateExistingPdmObject ), new RiaGrpcUnaryCallback( this, &Self::DeleteExistingPdmObject, &Self::RequestDeleteExistingPdmObject ), new RiaGrpcUnaryCallback( this, &Self::CreateChildPdmObject, &Self::RequestCreateChildPdmObject ), new RiaGrpcServerToClientStreamCallback( this, &Self::CallPdmObjectGetter, &Self::RequestCallPdmObjectGetter, new RiaPdmObjectMethodStateHandler ), new RiaGrpcClientToServerStreamCallback( this, &Self::CallPdmObjectSetter, &Self::RequestCallPdmObjectSetter, new RiaPdmObjectMethodStateHandler( true ) ), new RiaGrpcUnaryCallback( this, &Self::CallPdmObjectMethod, &Self::RequestCallPdmObjectMethod ), }; } static bool RiaGrpcPdmObjectService_init = RiaGrpcServiceFactory::instance()->registerCreator( typeid( RiaGrpcPdmObjectService ).hash_code() );