627 lines
19 KiB
Perl
627 lines
19 KiB
Perl
# --
|
|
# 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<https://otrs.org/>).
|
|
|
|
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<https://www.gnu.org/licenses/gpl-3.0.txt>.
|
|
|
|
=cut
|