# -- # Copyright (C) 2001-2019 OTRS AG, https://otrs.com/ # -- # This software comes with ABSOLUTELY NO WARRANTY. For details, see # the enclosed file COPYING for license information (GPL). If you # did not receive this file, see https://www.gnu.org/licenses/gpl-3.0.txt. # -- package Kernel::GenericInterface::Requester; use strict; use warnings; use Storable; use Kernel::GenericInterface::Debugger; use Kernel::GenericInterface::Invoker; use Kernel::GenericInterface::Mapping; use Kernel::GenericInterface::Transport; use Kernel::System::VariableCheck qw(:all); our @ObjectDependencies = ( 'Kernel::System::GenericInterface::Webservice', 'Kernel::System::Log', 'Kernel::GenericInterface::ErrorHandling', ); =head1 NAME Kernel::GenericInterface::Requester - GenericInterface handler for sending web service requests to remote providers =head1 PUBLIC INTERFACE =head2 new() create an object. Do not create it directly, instead use: my $RequesterObject = $Kernel::OM->Get('Kernel::GenericInterface::Requester'); =cut sub new { my ( $Type, %Param ) = @_; # allocate new hash for object my $Self = {}; bless( $Self, $Type ); return $Self; } =head2 Run() receives the current incoming web service request, handles it, and returns an appropriate answer based on the configured requested web service. my $Result = $RequesterObject->Run( WebserviceID => 1, # ID of the configured remote web service to use OR Invoker => 'some_operation', # Name of the Invoker to be used for sending the request Asynchronous => 1, # Optional, 1 or 0, defaults to 0 Data => { # Data payload for the Invoker request (remote web service) #... }, PastExecutionData => { # Meta data containing information about previous request attempts, optional #... } ); $Result = { Success => 1, # 0 or 1 ErrorMessage => '', # if an error occurred Data => { # Data payload of Invoker result (web service response) #... }, }; in case of an error if the request has been made asynchronously it can be re-schedule in future if the invoker returns the appropriate information $Result = { Success => 0, # 0 or 1 ErrorMessage => 'some error message', Data => { ReSchedule => 1, ExecutionTime => '2015-01-01 00:00:00', # optional }, }; =cut sub Run { my ( $Self, %Param ) = @_; for my $Needed (qw(WebserviceID Invoker Data)) { if ( !$Param{$Needed} ) { $Kernel::OM->Get('Kernel::System::Log')->Log( Priority => 'error', Message => "Got no $Needed!", ); return { Success => 0, ErrorMessage => "Got no $Needed!", }; } } # # Locate desired web service and load its configuration data. # my $WebserviceID = $Param{WebserviceID}; my $Webservice = $Kernel::OM->Get('Kernel::System::GenericInterface::Webservice')->WebserviceGet( ID => $WebserviceID, ); if ( !IsHashRefWithData($Webservice) ) { $Kernel::OM->Get('Kernel::System::Log')->Log( Priority => 'error', Message => "Could not load web service configuration for web service $Param{WebserviceID}", ); return { Success => 0, ErrorMessage => "Could not load web service configuration for web service $Param{WebserviceID}", }; } my $RequesterConfig = $Webservice->{Config}->{Requester}; # # Create a debugger instance which will log the details of this # communication entry. # my $DebuggerObject = Kernel::GenericInterface::Debugger->new( DebuggerConfig => $Webservice->{Config}->{Debugger}, WebserviceID => $WebserviceID, CommunicationType => 'Requester', ); if ( ref $DebuggerObject ne 'Kernel::GenericInterface::Debugger' ) { return { Success => 0, ErrorMessage => "Could not initialize debugger", }; } $DebuggerObject->Debug( Summary => 'Communication sequence started', Data => $Param{Data}, ); # # Create Invoker object and prepare the request on it. # $DebuggerObject->Debug( Summary => "Using invoker '$Param{Invoker}'", ); my $InvokerObject = Kernel::GenericInterface::Invoker->new( DebuggerObject => $DebuggerObject, Invoker => $Param{Invoker}, InvokerType => $RequesterConfig->{Invoker}->{ $Param{Invoker} }->{Type}, WebserviceID => $WebserviceID, ); # Bail out if invoker initialization failed. if ( ref $InvokerObject ne 'Kernel::GenericInterface::Invoker' ) { return $DebuggerObject->Error( Summary => 'InvokerObject could not be initialized', Data => $InvokerObject, ); } # Prepare the data include configuration and payload. my %DataInclude = ( RequesterRequestInput => $Param{Data}, ); # Combine all data for error handler we got so far. my %HandleErrorData = ( InvokerObject => $InvokerObject, Invoker => $Param{Invoker}, DebuggerObject => $DebuggerObject, WebserviceID => $WebserviceID, WebserviceConfig => $Webservice->{Config}, PastExecutionData => $Param{PastExecutionData}, ); my $FunctionResult = $InvokerObject->PrepareRequest( Data => $Param{Data}, ); if ( !$FunctionResult->{Success} ) { my $Summary = $FunctionResult->{ErrorMessage} // 'InvokerObject returned an error, cancelling Request'; return $Self->_HandleError( %HandleErrorData, DataInclude => \%DataInclude, ErrorStage => 'RequesterRequestPrepare', Summary => $Summary, Data => $FunctionResult->{Data} // $Summary, ); } # Not always a success on the invoker prepare request means that invoker need to do something # there are cases in which the requester does not need to do anything, for this cases # StopCommunication can be sent. in this cases the request will be successful with out sending # the request actually. elsif ( $FunctionResult->{StopCommunication} && $FunctionResult->{StopCommunication} eq 1 ) { return { Success => 1, }; } # Extend the data include payload/ $DataInclude{RequesterRequestPrepareOutput} = $FunctionResult->{Data}; # # Map the outgoing data. # my $DataOut = $FunctionResult->{Data}; $DebuggerObject->Debug( Summary => "Outgoing data before mapping", Data => $DataOut, ); # Decide if mapping needs to be used or not. if ( IsHashRefWithData( $RequesterConfig->{Invoker}->{ $Param{Invoker} }->{MappingOutbound} ) ) { my $MappingOutObject = Kernel::GenericInterface::Mapping->new( DebuggerObject => $DebuggerObject, Invoker => $Param{Invoker}, InvokerType => $RequesterConfig->{Invoker}->{ $Param{Invoker} }->{Type}, MappingConfig => $RequesterConfig->{Invoker}->{ $Param{Invoker} }->{MappingOutbound}, ); # If mapping initialization failed, bail out. if ( ref $MappingOutObject ne 'Kernel::GenericInterface::Mapping' ) { $DebuggerObject->Error( Summary => 'MappingOut could not be initialized', Data => $MappingOutObject, ); return $DebuggerObject->Error( Summary => $FunctionResult->{ErrorMessage}, ); } $FunctionResult = $MappingOutObject->Map( Data => $DataOut, DataInclude => \%DataInclude, ); if ( !$FunctionResult->{Success} ) { my $Summary = $FunctionResult->{ErrorMessage} // 'MappingOutObject returned an error, cancelling Request'; return $Self->_HandleError( %HandleErrorData, DataInclude => \%DataInclude, ErrorStage => 'RequesterRequestMap', Summary => $Summary, Data => $FunctionResult->{Data} // $Summary, ); } # Extend the data include payload. $DataInclude{RequesterRequestMapOutput} = $FunctionResult->{Data}; $DataOut = $FunctionResult->{Data}; $DebuggerObject->Debug( Summary => "Outgoing data after mapping", Data => $DataOut, ); } my $TransportObject = Kernel::GenericInterface::Transport->new( DebuggerObject => $DebuggerObject, TransportConfig => $RequesterConfig->{Transport}, ); # Bail out if transport initialization failed. if ( ref $TransportObject ne 'Kernel::GenericInterface::Transport' ) { return $DebuggerObject->Error( Summary => 'TransportObject could not be initialized', Data => $TransportObject, ); } # Read request content. $FunctionResult = $TransportObject->RequesterPerformRequest( Operation => $Param{Invoker}, Data => $DataOut, ); my $IsAsynchronousCall = $Param{Asynchronous} ? 1 : 0; if ( !$FunctionResult->{Success} ) { my $Summary = $FunctionResult->{ErrorMessage} // 'TransportObject returned an error, cancelling Request'; my $ErrorReturn = $Self->_HandleError( %HandleErrorData, DataInclude => \%DataInclude, ErrorStage => 'RequesterRequestPerform', Summary => $Summary, Data => $FunctionResult->{Data} // $Summary, ); # Send error to Invoker. my $Response = $InvokerObject->HandleResponse( ResponseSuccess => 0, ResponseErrorMessage => $FunctionResult->{ErrorMessage}, ); if ($IsAsynchronousCall) { RESPONSEKEY: for my $ResponseKey ( sort keys %{$Response} ) { # Skip Success and ErrorMessage as they are set already. next RESPONSEKEY if $ResponseKey eq 'Success'; next RESPONSEKEY if $ResponseKey eq 'ErrorMessage'; # Add any other key from the invoker HandleResponse() in Data. $ErrorReturn->{$ResponseKey} = $Response->{$ResponseKey}; } } return $ErrorReturn; } # Extend the data include payload. $DataInclude{RequesterResponseInput} = $FunctionResult->{Data}; my $DataIn = $FunctionResult->{Data}; my $SizeExeeded = $FunctionResult->{SizeExeeded} || 0; if ($SizeExeeded) { $DebuggerObject->Debug( Summary => "Incoming data before mapping was too large for logging", Data => 'See SysConfig option GenericInterface::Operation::ResponseLoggingMaxSize to change the maximum.', ); } else { $DebuggerObject->Debug( Summary => "Incoming data before mapping", Data => $DataIn, ); } # Decide if mapping needs to be used or not. if ( IsHashRefWithData( $RequesterConfig->{Invoker}->{ $Param{Invoker} }->{MappingInbound} ) ) { my $MappingInObject = Kernel::GenericInterface::Mapping->new( DebuggerObject => $DebuggerObject, Invoker => $Param{Invoker}, InvokerType => $RequesterConfig->{Invoker}->{ $Param{Invoker} }->{Type}, MappingConfig => $RequesterConfig->{Invoker}->{ $Param{Invoker} }->{MappingInbound}, ); # If mapping initialization failed, bail out. if ( ref $MappingInObject ne 'Kernel::GenericInterface::Mapping' ) { $DebuggerObject->Error( Summary => 'MappingOut could not be initialized', Data => $MappingInObject, ); return $DebuggerObject->Error( Summary => $FunctionResult->{ErrorMessage}, ); } $FunctionResult = $MappingInObject->Map( Data => $DataIn, DataInclude => \%DataInclude, ); if ( !$FunctionResult->{Success} ) { my $Summary = $FunctionResult->{ErrorMessage} // 'MappingInObject returned an error, cancelling Request'; return $Self->_HandleError( %HandleErrorData, DataInclude => \%DataInclude, ErrorStage => 'RequesterResponseMap', Summary => $Summary, Data => $FunctionResult->{Data} // $Summary, ); } # Extend the data include payload. $DataInclude{RequesterResponseMapOutput} = $FunctionResult->{Data}; $DataIn = $FunctionResult->{Data}; if ($SizeExeeded) { $DebuggerObject->Debug( Summary => "Incoming data after mapping was too large for logging", Data => 'See SysConfig option GenericInterface::Operation::ResponseLoggingMaxSize to change the maximum.', ); } else { $DebuggerObject->Debug( Summary => "Incoming data after mapping", Data => $DataIn, ); } } # # Handle response data in Invoker. # $FunctionResult = $InvokerObject->HandleResponse( ResponseSuccess => 1, Data => $DataIn, ); if ( !$FunctionResult->{Success} ) { my $Summary = $FunctionResult->{ErrorMessage} // 'InvokerObject returned an error, cancelling Request'; my $ErrorReturn = $Self->_HandleError( %HandleErrorData, DataInclude => \%DataInclude, ErrorStage => 'RequesterResponseProcess', Summary => $Summary, Data => $FunctionResult->{Data} // $Summary, ); if ($IsAsynchronousCall) { RESPONSEKEY: for my $ResponseKey ( sort keys %{$FunctionResult} ) { # Skip Success and ErrorMessage as they are set already. next RESPONSEKEY if $ResponseKey eq 'Success'; next RESPONSEKEY if $ResponseKey eq 'ErrorMessage'; # Add any other key from the invoker HandleResponse() in Data. $ErrorReturn->{$ResponseKey} = $FunctionResult->{$ResponseKey}; } } return $ErrorReturn; } $DataIn = $FunctionResult->{Data}; return { Success => 1, Data => $DataIn, }; } =head2 _HandleError() handles errors by - informing invoker about it (if supported) - calling an error handling layer my $ReturnData = $RequesterObject->_HandleError( InvokerObject => $InvokerObject, Invoker => 'InvokerName', DebuggerObject => $DebuggerObject, WebserviceID => 1, WebserviceConfig => $WebserviceConfig, DataInclude => $DataIncludeStructure, ErrorStage => 'PrepareRequest', # at what point did the error occur? Summary => 'an error occurred', Data => $ErrorDataStructure, PastExecutionData => $PastExecutionDataStructure, # optional ); my $ReturnData = { Success => 0, ErrorMessage => $Param{Summary}, }; =cut sub _HandleError { my ( $Self, %Param ) = @_; NEEDED: for my $Needed ( qw(InvokerObject Invoker DebuggerObject WebserviceID WebserviceConfig DataInclude ErrorStage Summary Data) ) { next NEEDED if $Param{$Needed}; $Kernel::OM->Get('Kernel::System::Log')->Log( Priority => 'error', Message => "Got no $Needed!", ); return { Success => 0, ErrorMessage => "Got no $Needed!", }; } my $ErrorHandlingResult = $Kernel::OM->Get('Kernel::GenericInterface::ErrorHandling')->HandleError( WebserviceID => $Param{WebserviceID}, WebserviceConfig => $Param{WebserviceConfig}, CommunicationID => $Param{DebuggerObject}->{CommunicationID}, CommunicationType => 'Requester', CommunicationName => $Param{Invoker}, ErrorStage => $Param{ErrorStage}, Summary => $Param{Summary}, Data => $Param{Data}, PastExecutionData => $Param{PastExecutionData}, ); my $ReturnData = { Success => 0, ErrorMessage => $ErrorHandlingResult->{ErrorMessage} || $Param{Summary}, Data => $ErrorHandlingResult->{ReScheduleData}, }; return $ReturnData if !$Param{InvokerObject}->{BackendObject}->can('HandleError'); my $HandleErrorData; if ( !defined $Param{Data} || IsString( $Param{Data} ) ) { $HandleErrorData = $Param{Data} // ''; } else { $HandleErrorData = Storable::dclone( $Param{Data} ); } $Param{DebuggerObject}->Debug( Summary => 'Error data before mapping', Data => $HandleErrorData, ); # TODO: Use separate mapping config for errors. my $InvokerConfig = $Param{WebserviceConfig}->{Requester}->{Invoker}->{ $Param{Invoker} }; if ( IsHashRefWithData( $InvokerConfig->{MappingInbound} ) ) { my $MappingErrorObject = Kernel::GenericInterface::Mapping->new( DebuggerObject => $Param{DebuggerObject}, Invoker => $Param{Invoker}, InvokerType => $InvokerConfig->{Type}, MappingConfig => $InvokerConfig->{MappingInbound}, ); # If mapping init failed, bail out. if ( ref $MappingErrorObject ne 'Kernel::GenericInterface::Mapping' ) { $Param{DebuggerObject}->Error( Summary => 'MappingErr could not be initialized', Data => $MappingErrorObject, ); return $ReturnData; } # Map error data. my $MappingErrorResult = $MappingErrorObject->Map( Data => { Fault => $HandleErrorData, }, DataInclude => { %{ $Param{DataInclude} }, RequesterErrorHandlingOutput => $ErrorHandlingResult->{Data}, }, ); if ( !$MappingErrorResult->{Success} ) { $Param{DebuggerObject}->Error( Summary => $MappingErrorResult->{ErrorMessage}, ); return $ReturnData; } $HandleErrorData = $MappingErrorResult->{Data}; $Param{DebuggerObject}->Debug( Summary => 'Error data after mapping', Data => $HandleErrorData, ); } my $InvokerHandleErrorOutput = $Param{InvokerObject}->HandleError( Data => $HandleErrorData, ); if ( !$InvokerHandleErrorOutput->{Success} ) { $Param{DebuggerObject}->Error( Summary => 'Error handling error data in Invoker', Data => $InvokerHandleErrorOutput->{ErrorMessage}, ); } return $ReturnData; } 1; =head1 TERMS AND CONDITIONS This software is part of the OTRS project (L). This software comes with ABSOLUTELY NO WARRANTY. For details, see the enclosed file COPYING for license information (GPL). If you did not receive this file, see L. =cut