sap-message header in function import call - sap-cloud-sdk

we are firing a draft activation call(submit call) to s4 system using the cloud sdk(java). S4 odata service in odata v2.0. The submit call is a function import
with the following signature -
<FunctionImport
Name="C_GuidedProcmtReqnHdrTPActivation"
ReturnType="MMPUR_REQ_GPR_MAINTAIN_SRV.C_GuidedProcmtReqnHdrTPType"
EntitySet="C_GuidedProcmtReqnHdrTP" m:HttpMethod="POST"
sap:action-for="MMPUR_REQ_GPR_MAINTAIN_SRV.C_GuidedProcmtReqnHdrTPType"
sap:applicable-path="Activation_ac">
<Parameter Name="PurchaseRequisition" Type="Edm.String"
Mode="In" MaxLength="10" />
<Parameter Name="DraftUUID" Type="Edm.Guid" Mode="In" />
<Parameter Name="IsActiveEntity" Type="Edm.Boolean"
Mode="In" />
</FunctionImport>
Using vdm we are executing the following call
HeaderCDSForPRForGuidedBuying requisitionHeader = s4ReqService
.guidedProcmtReqnHdrTPActivation(Constants.EMPTY_STRING, UUID.fromString(draftReqId.toString()), false)
.cachingMetadata().execute(getS4Destination());
However, we are not able to receive the sap-message header in this call. The question is how to receive
the sap-message for this function import?
we are using cloud SDK version 3.21.0

Update:
As of SAP Cloud SDK 3.34.1 we provide an improved function import logic for OData V2. We offer the method yourFunctionImport.toRequest() which gives access to the internally built OData request object.
You can use this request object to invoke the function import and to retrieve a Java representation of the function import result, including the response headers.
Code snippet:
final ODataRequestGeneric odataRequest = s4ReqService
.guidedProcmtReqnHdrTPActivation(Constants.EMPTY_STRING, UUID.fromString(draftReqId.toString()), false)
.toRequest();
final ODataRequestResult odataResult =
odataRequest.execute(HttpClientAccessor.getHttpClient(destination));
final Iterable<String> headerNames = odataResult.getHeaderNames();
final Iterable<String> headerValues = odataResult.getHeaderValues("header-key);
Outdated:
At the time of this writing, accessing HTTP response headers of function import calls when using the OData VDM is not possible. I'll update this answer, in case this feature is available in a newer version.
For the time being, we propose building the request manually as workaround.
Let me sketch this out for Function Imports with GET requests.
Generic pattern:
final ODataRequestRead functionImportRequest = new ODataRequestRead("service-url", "function-import-name", "query-string", ODataProtocol.V2);
final ODataRequestResultGeneric functionImportResponse = functionImportCall.execute(HttpClientAccessor.getHttpClient(destination));
final Iterable<String> headerNames = functionImportResponse.getHeaderNames();
final Iterable<String> headerValues = functionImportResponse.getHeaderValues("response-header-key");
Applying that generic pattern to a concrete function import of a concrete service:
final ODataRequestRead functionImportRequest = new ODataRequestRead("/sap/opu/odata/sap/API_CV_ATTACHMENT_SRV/", "GetAttachmentCount", "BusinessObjectTypeName='1000'&LinkedSAPObjectKey='2000'&SemanticObject='3000'", ODataProtocol.V2);
final ODataRequestResultGeneric functionImportResponse = functionImportCall.execute(HttpClientAccessor.getHttpClient(destination));
//use functionImportResponse.getHeaderNames() or functionImportResponse.getHeaderValues(key)
If the function import requires POST request, additional CSRF token handling must be added.
The drawback of this workaround is that it neglects the type-safety that the VDM provides you.

Related

Shopware How to get SalesChannelContext in OrderWrittenEvent in order to send Mail?

I have a situation where I want to send emails from the order written event whenever an order has been updated according to some set of conditions that I will implement (for example an API response error) But unfortunately I have been unable to do so.
I first created a controller and an email service which uses the abstract email service of shopware And from my controller I'm able to send an email But when I tried to do the same in the event,I quickly realized that it wasn't doing exactly what I was expecting it to do. After some research on it, I saw that the event actually don't have access to the sales channel context so I tried multiple different ways to solve this issue but I'm still stuck. Can somebody please guide me on how I can implement that? thank you very much.
an example of what I tried is to call the store API in order to get the context of the saleschannel to use it in my sendMail function But it was giving errors such as:
request.CRITICAL: Uncaught PHP Exception TypeError: "Argument 5 passed to Swag\BasicExample\Service\EmailService::sendMail() must be an instance of Shopware\Core\System\SalesChannel\SalesChannelContext, instance of stdClass given.
I obviously understand that I have to give it a Shopware\Core\System\SalesChannel\SalesChannelContext not an STD class but how can I do that? since it doesn't really see the channel context.
If you do have an instance of OrderEntity you can rebuild the SalesChannelContext from the existing order using the OrderConverter service.
<service id="Foo\MyPlugin\Subscriber\MySubscriber">
<argument type="service" id="Shopware\Core\Checkout\Cart\Order\OrderConverter"/>
<argument type="service" id="order.repository"/>
<tag name="kernel.event_subscriber"/>
</service>
class MySubscriber implements EventSubscriberInterface
{
private OrderConverter $orderConverter;
private EntityRepository $repository;
public function __construct(
OrderConverter $orderConverter,
EntityRepository $repository
) {
$this->orderConverter = $orderConverter;
$this->repository = $repository;
}
public static function getSubscribedEvents(): array
{
return [
OrderEvents::ORDER_WRITTEN_EVENT => 'onOrderWritten',
];
}
public function onOrderWritten(EntityWrittenEvent $event): void
{
foreach ($event->getWriteResults() as $writeResult) {
$orderId = $writeResult->getPrimaryKey();
$criteria = new Criteria([$orderId]);
$criteria->addAssociation('transactions');
$criteria->addAssociation('orderCustomer');
$order = $this->repository
->search($criteria, $event->getContext())->first();
if (!$order instanceof OrderEntity) {
continue;
}
$salesChannelContext = $this->orderConverter
->assembleSalesChannelContext($order, $event->getContext());
// ...
}
}
}

Mockito with newInstance method

I have a class-under-test that has the following code :
public void getDetails (String message){
DocumentBuilderFactory docBuilderFactory = DocumentBuilderFactory.newInstance();
DocumentBuilder docBuilder = docBuilderFactory.newDocumentBuilder();
StringReader strReader = new StringReader(message);
InputSource inputSrc = new InputSource(strReader);
Document doc = docBuilder.parse(inputSrc);
...
}
I want to write a JUnit for this piece of code using Mockito.
I tried various things like :
DocumentBuilderFactory docBuilderFactoryMock = Mockito.mock(DocumentBuilderFactory.class);
Mockito.when(DocumentBuilderFactory.newInstance()).thenReturn(docBuilderFactoryMock);
But I get the Exception:
org.mockito.exceptions.misusing.WrongTypeOfReturnValue:
DocumentBuilderFactory$$EnhancerByMockitoWithCGLIB$$23223735 cannot be returned by toString()
toString() should return String
If you're unsure why you're getting above error read on.
Due to the nature of the syntax above problem might occur because:
1. This exception *might* occur in wrongly written multi-threaded tests.
Please refer to Mockito FAQ on limitations of concurrency testing.
2. A spy is stubbed using when(spy.foo()).then() syntax. It is safer to stub spies -
- with doReturn|Throw() family of methods. More in javadocs for Mockito.spy() method.
If I do the following:
DocumentBuilderFactory docBuilderFactoryMock = Mockito.spy(DocumentBuilderFactory.newInstance());
DocumentBuilder documentBuilderMock = Mockito.mock(DocumentBuilder.class);
Mockito.when(docBuilderFactory.newDocumentBuilder()).thenReturn(documentBuilderMock);
docBuilderdocBuilderMockito.when(docBuilderFactoryMock.newDocumentBuilder()).thenReturn(docBuilderFactoryMock);
and debug my code then I see that the class-under-test does not use my Mock objects anywhere but creates its own objects and throws a SAXParseException at
Document doc = docBuilder.parse(inputSrc);
Unit testing is intended to test your components/classes, not the library components/classes that are used.
Your class is parsing a String as xml content and to test that, you will find that providing a set of xml Strings with known output is the best way to test.
Simply pass a known xml String to your class under test and assert that the resulting model that is parsed, contains the data that you expect for that xml content.
I don't think that you need any mocking.

When overriding default configuration for Date Serialization, it becomes missing in the JSON example in metadata pages

I am attempting to override the default DateTime serialization with the following code:
JsConfig<DateTime>.SerializeFn = d =>
{
return d.ToString("o") + "Z";
};
JsConfig<DateTime>.RawSerializeFn = d =>
{
return d.ToString("o") + "Z";
};
(not sure the diff between SerializeFn and RawSerializeFn so i tried both to be sure...I also tried implementing the DeserializeFn in case they both needed to be overwritten, but saw some results)
Anyways... everytime I try this, any date members in our DTOs goes missing in the sample request/response JSON on the metadata pages. (date members still show in the Parameters section though).
I am using SS v4.0.40.0
PS: I later realized that my whole goal of appending "Z" to all DateTimes could be accomplished with this configuration:
JsConfig.DateHandler = DateHandler.ISO8601;
JsConfig.AssumeUtc = true;
JsConfig.AppendUtcOffset = false;
but I still wanted to file this bug - Thanks!
The DateTime serialization can't be changed in isolation, if you take over serializing to an unsupported custom format you'll also need to handle deserializing it with the appropriate RawDeserializeFn/DeserializeFn configuration.
You can also handle parsing unknown DateTime formats by registering a ParseError callback, i.e:
DateTimeSerializer.OnParseErrorFn = (dateTimeStr, ex) => //DateTime;
If you want to file an issue with any ServiceStack libraries, upgrade to the latest version of ServiceStack to ensure it's still an issue, if it is please submit it to github.com/ServiceStack/Issues with a sample code/failing test that reproduces the issue.

No routing convention was found to select an action for the OData path with template '~/entityset'

I have two Odata action methods defined. The one with parameter gets invoked while the other without parameter doesnt get invoked and throws error No routing convention was found to select an action for the OData path with template '~/entityset'.
Here is the code of my action methods
[EnableQuery]
public IQueryable<User> GetUser()
{
return db.Users;
}
// GET: odata/User(5)
[EnableQuery]
public SingleResult<User> GetUser([FromODataUri] int key)
{
return SingleResult.Create(db.Users.Where(user => user.Id == key));
}
The query that I am using are as follows
http://bureauservice/api/odata/UserOdata - Doesnt work
http://bureauservice/api/odata/UserOdata(1) - works
Could someone tell me why the first link doesnt work.
Please change the name of the method which returns entityset to "Get[EntitySetName]" or "Get".
Change from
public IQueryable<User> GetUser()
To
public IQueryable<User> GetUserOdata()
Or
public IQueryable<User> Get()
Set the name of the first action as GetUsers (plural) because you are getting the whole collection of users while in the second you are asking for a single user.
You may want to add the parenthesis to the first URL:
http://bureauservice/api/odata/UserOdata()
If you are just starting to proactise odata, then Odata v4 is good start point, as it is an OASIS standard, but v3 is not.
Here is the v4 version Function sample:
https://github.com/OData/ODataSamples/tree/master/WebApiCore/ODataFunctionSample.

CRM 2011 PLUGIN to update another entity

My PLUGIN is firing on Entity A and in my code I am invoking a web service that returns an XML file with some attributes (attr1,attr2,attr3 etc ...) for Entity B including GUID.
I need to update Entity B using the attributes I received from the web service.
Can I use Service Context Class (SaveChanges) or what is the best way to accomplish my task please?
I would appreciate it if you provide an example.
There is no reason you need to use a service context in this instance. Here is basic example of how I would solve this requirement. You'll obviously need to update this code to use the appropriate entities, implement your external web service call, and handle the field updates. In addition, this does not have any error checking or handling as should be included for production code.
I made an assumption you were using the early-bound entity classes, if not you'll need to update the code to use the generic Entity().
class UpdateAnotherEntity : IPlugin
{
private const string TARGET = "Target";
public void Execute(IServiceProvider serviceProvider)
{
//PluginSetup is an abstraction from: http://nicknow.net/dynamics-crm-2011-abstracting-plugin-setup/
var p = new PluginSetup(serviceProvider);
var target = ((Entity) p.Context.InputParameters[TARGET]).ToEntity<Account>();
var updateEntityAndXml = GetRelatedRecordAndXml(target);
var relatedContactEntity =
p.Service.Retrieve(Contact.EntityLogicalName, updateEntityAndXml.Item1, new ColumnSet(true)).ToEntity<Contact>();
UpdateContactEntityWithXml(relatedContactEntity, updateEntityAndXml.Item2);
p.Service.Update(relatedContactEntity);
}
private static void UpdateContactEntityWithXml(Contact relatedEntity, XmlDocument xmlDocument)
{
throw new NotImplementedException("UpdateContactEntityWithXml");
}
private static Tuple<Guid, XmlDocument> GetRelatedRecordAndXml(Account target)
{
throw new NotImplementedException("GetRelatedRecordAndXml");
}
}

Resources