Generating XML output with XQuery / XPath expression matching nothing, why? - xquery

I am trying to generate the following xml output:
<OrderDataUpdate xmlns=”http://www.metasolv.com/OMS/OrderDataUpdate/2002/10/25”>
<TetheringType>IE Tethering</TetheringType>
</OrderDataUpdate>
But instead I get this(note the text content for TetheringType is not populated).
<OrderDataUpdate xmlns="http://www.metasolv.com/OMS/OrderDataUpdate/2002/10/25">
<TetheringType/>
</OrderDataUpdate>
My input XML:
<soapenv:Envelope
xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<soapenv:Header/>
<soapenv:Body>
<ser:serviceLookUpResponse
xmlns:ser="http://xmlns.oracle.com/communications/inventory/webservice/service">
<com:requestContext xsi:nil="true"
xmlns:com="http://xmlns.oracle.com/communications/inventory/webservice/common"/>
<com:messages xmlns:com="http://xmlns.oracle.com/communications/inventory/webservice/common"
>Success</com:messages>
<service>
<id>12021409</id>
<name>1</name>
<description xsi:nil="true"/>
<state>in service</state>
<startDate>2013-11-06T00:13:02.000-06:00</startDate>
<specification>
<name>HSPA Wireless Service</name>
<entityType>Service</entityType>
<description xsi:nil="true"/>
<startDate>2010-05-13T00:00:01.000-05:00</startDate>
<endDate xsi:nil="true"/>
</specification>
<parties>
<id>1-3BQJ7K</id>
<name>MTSC NETWORK SRV WIRELESS-CELLULAR</name>
<description xsi:nil="true"/>
</parties>
<configurations>
<version>31</version>
<previousVersion>30</previousVersion>
<id>Se_12021409_31</id>
<name>Se_12021409_31</name>
<description xsi:nil="true"/>
<state>completed</state>
<startDate>2015-01-21T15:03:52.000-06:00</startDate>
<endDate xsi:nil="true"/>
<configurationItems>
<name>Entitlement</name>
<index>0</index>
<resourceAssignment>
<state>assigned</state>
<resource>
<id>Tethering</id>
<name xsi:nil="true"/>
<description>Tethering Entitlement</description>
<specification>
<name>Entitlement</name>
<entityType>Custom Network Address</entityType>
<description xsi:nil="true"/>
<startDate>2015-01-15T00:00:01.000-06:00</startDate>
<endDate xsi:nil="true"/>
</specification>
</resource>
</resourceAssignment>
<resourceReference xsi:nil="true"/>
<characteristics>
<name>Tethering Type</name>
<value>IE Tethering</value>
</characteristics>
</configurationItems>
</configurations>
</service>
</ser:serviceLookUpResponse>
</soapenv:Body>
</soapenv:Envelope>
Xquery:
declare namespace soap = "http://schemas.xmlsoap.org/soap/envelope/";
declare namespace ser = "http://xmlns.oracle.com/communications/inventory/webservice/service";
declare namespace oms = "urn:com:metasolv:oms:xmlapi:1";
declare namespace output = "http://www.w3.org/2010/xslt-xquery-serialization";
declare option output:method "xml";
declare option output:indent "yes";
declare function local:generateUpdate($uimResponse as element()*) as element()
{
let $update :=
<OrderDataUpdate xmlns="http://www.metasolv.com/OMS/OrderDataUpdate/2002/10/25">
<TetheringType>{ $uimResponse//characteristics[name='Tethering Type']/value/text() }</TetheringType>
</OrderDataUpdate>
return
$update
};
let $uimResponse := fn:root(.)/soap:Envelope/soap:Body/ser:serviceLookUpResponse
let $mdn := $uimResponse//configurationItems[name = 'MDN']/resourceAssignment/resource/name/text()
let $output := local:generateUpdate($uimResponse)
return
(
$output
)
If I modify the function local:generateUpdate to remove xmlns="http://www.metasolv.com/OMS/OrderDataUpdate/2002/10/25", then text content of TetheringType gets populated in the generated output xml which appears something like below:
<OrderDataUpdate>
<TetheringType>IE Tethering</TetheringType>
</Add>
</OrderDataUpdate>
What am I doing wrong? Can someone explain to me why is it that on adding xmlns=”http://www.metasolv.com/OMS/OrderDataUpdate/2002/10/25” to the OrderDataUpdate element, the text content of xml nodes are not populated in the xml output. I need the <OrderDataUpdate> to be associated with the namespace "http://www.metasolv.com/OMS/OrderDataUpdate/2002/10/25"
Any help would be appreciated.
Thanks in advance.

At first glance, it looks as though by declaring a default namespace on your OrderDataUpdate element, you have caused a change in the interpretation of the unprefixed names characteristics, name, and value which occur lexically within that element. Your TetheringType element gets no text content because the XPath is matching nothing (there are no characteristics element in the namespace http://www.metasolv.com/OMS/OrderDataUpdate/2002/10/25 in your input).
On second glance, this hypothesis is confirmed by the XPath spec and by the behavior of the XQuery processors I have checked with.
There are at least two simple ways to fix your problem. First, you could avoid the name capture by not declaring a default namespace:
declare function local:generateUpdate(
$uimResponse as element()*
) as element() {
let $update := <odu:OrderDataUpdate
xmlns:odu=
"http://www.metasolv.com/OMS/OrderDataUpdate/2002/10/25">
<odu:TetheringType>{
$uimResponse//characteristics[name='Tethering Type']
/value/text()
}</odu:TetheringType>
</odu:OrderDataUpdate>
return $update
};
Or you could just move the calculation of the text out of the scope of the default namespace declaration:
declare function local:generateUpdate(
$uimResponse as element()*
) as element() {
let $tttext := $uimResponse
//characteristics[name='Tethering Type']
/value/text()
let $update := <OrderDataUpdate
xmlns="http://www.metasolv.com/OMS/OrderDataUpdate/2002/10/25">
<TetheringType>{
$tttext
}</TetheringType>
</OrderDataUpdate>
return $update
};

Related

How to omit null values in JSON AssignMessage payload in APIGEE?

I built a proxy that basically expects a different JSON input object than the one the final endpoint is expecting to receive. So, in order to bridge the request object from one to the other I'm using an AssingMessage policy to transform the json input.
I'm doing something like this:
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<AssignMessage async="false" continueOnError="false" enabled="true" name="Assign-Message-Sample">
<DisplayName>Assign Message-Sample</DisplayName>
<Remove>
<Headers>
<Header name="login_id"/>
<Header name="Authorization"/>
</Headers>
<Payload>true</Payload>
</Remove>
<Set>
<Payload contentType="application/json">
{
"valueA": "{clientrequest.valueA}",
"valueB": "{clientrequest.valueB}",
"valueC": "{clientrequest.valueC}",
"valueD": "{clientrequest.valueD}",
"valueE": "{clientrequest.valueE}",
"valueF": "{clientrequest.valueF}",
}
</Payload>
<Verb>POST</Verb>
</Set>
<IgnoreUnresolvedVariables>true</IgnoreUnresolvedVariables>
<AssignTo createNew="false" transport="http" type="request"/>
</AssignMessage>
The problem comes when some of the values are empty. The destination server does not handle properly any empty values (escapes from my control).
My question is: how can I skip entirely a parameter if value is empty?
I'm looking for something like this (or better alternative):
<Payload contentType="application/json">
{
<skip-if-empty name="clientrequest.valueA">
"valueA": "{clientrequest.valueA}",
</skip-if-empty>
"valueB": "{clientrequest.valueB}",
...
}
</Payload>
For what I have found from my research, it seems this is a job for a Javascript Policy.
How is this done?
You basically need to place a javascript policy right before the AssignMessage execution. In the javascript policy you have the freedom to apply all the logic to omit certain parameters if values are not provided.
So for example, say we have already extracted the request values to variables using an ExtractVariables policy. Then, in the Javascript policy we can validate those values and build the resulting JSON object to later store it in another variable that will be picked up by the AssingMessage policy.
javascript policy:
var valueA = context.getVariable("clientrequest.valueA"),
valueB = context.getVariable("clientrequest.valueB"),
valueC = context.getVariable("clientrequest.valueC"),
...
var result = {};
if(valueB) {
result.b = valueB;
}
if(valueA) {
result.a = valueA;
}
if(valueC) {
result.c = valueC;
}
...
context.setVariable("newInput", JSON.stringify(result));
Then our AssignMessage will just pick up the variable we just stored: "newInput" that will contain the complete JSON object string:
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<AssignMessage async="false" continueOnError="false" enabled="true" name="Assign-Message-Sample">
<DisplayName>Assign Message-Sample</DisplayName>
<Remove>
<Headers>
<Header name="login_id"/>
<Header name="Authorization"/>
</Headers>
<Payload>true</Payload>
</Remove>
<Set>
<Payload contentType="application/json">
{newInput}
</Payload>
<Verb>POST</Verb>
</Set>
<IgnoreUnresolvedVariables>true</IgnoreUnresolvedVariables>
<AssignTo createNew="false" transport="http" type="request"/>
</AssignMessage>
This solution worked fine for me. I hope someone else finds it helpful.

What is the best practice to transform the path between proxy and target in apigee?

I am making a request /Patient/1 at the proxy endpoint, and I want to transform this into target endpoint /openemr/api. I tried solutions given in https://community.apigee.com/articles/2793/how-to-geo-locate-calls-to-target-endpoint.html, but none of these worked for me. Please suggest an optimal way for achieving this.
Thanks in advance.
It depends on how many reroutes you have to implement. So in case of 5+, use a javascript policy, such as:
var reqPathsuffix = context.getVariable("proxy.pathsuffix");
var reqVerb = context.getVariable("request.verb");
if ((reqPathsuffix === "/contact") && (reqVerb === "GET"))
{
endPointURL = "https://yourendpointurl/
}
context.setVariable("target.url",endPointURL);
else maybe use a 'AssignMessage' policy.
<AssignMessage async="false" continueOnError="false" enabled="true" name="Assign-Message-1">
<AssignTo createNew="false" transport="http" type="request"/>
<AssignVariable>
<Name>target.url</Name>
<Value>http://google.nl</Value>
</AssignVariable>
Don't forget to do this on the 'target endpoint preflow request' flow. Else it won't work.

Using AssignMessage policy to generate a formatted string in Apigee

I am doing a URL rewrite in my API End point. So, the querystring is not passed by default to the backend service.
I want to create a variable with name "querystring" and format that string with the input query params. How can I do that?
For eg: apigee api url = https://myapigee-prod.agigee.net/v1/test/resource1?p1=abc&p2=123
I want to create the querystring variable as querystring = param1=abc&param2=123
I tried
<AssignVariable>
<Name>myqs</Name>
<Value>param1={request.queryparam.p1}&param2={request.queryparam.p2}</Value>
</AssignVariable>
<Ref> tag can be used instead of <Value> tag only if I am getting value of one parameter. To form a string, it wouldn't work.
Unfortunately you can't use the curly braces method of variable expansion within an AssignVariable.
You can do this via a JavaScript policy. Here is the JavaScript code:
var myqs = "param1=" + context.getVariable("request.queryparam.p1") +
"&param2=" + context.getVariable("request.queryparam.p2");
context.setVariable("myqs", myqs);
Also, you can use an AssignMessage policy to do this, on the Proxy request or Target request flow.
http://apigee.com/docs/api-services/content/generate-or-modify-messages-using-assignmessage
e.g.
<AssignMessage name="GenerateRequest">
<AssignTo createNew="false" type="request">Request</AssignTo>
<Set>
<QueryParams>
<QueryParam name="p1">{request.queryparam.param1}</QueryParam>
<QueryParam name="p2">{request.queryparam.param2}</QueryParam>
</QueryParams>
</Set>
</AssignMessage>

How to add basic auth to a service callout policy

This is the service callout policy:
<ServiceCallout name="GeoCodeClient">
<Request clearPayload="false" variable="GeocodingRequest" />
<Response>GeocodingResponse</Response>
<Timeout>30000</Timeout>
<HTTPTargetConnection>
<URL>http://maps.googleapis.com/maps/api/geocode/json</URL>
</HTTPTargetConnection>
</ServiceCallout>
Let us say I have to access a resource that is username/password protected. How do I add that basic authorization to this policy to enable me to do that?
In our project a KeyValueMaps are used to store the basic auth info at org level. The authorisation information is retrieved using the KeyValueMap policy and added as the basic auth header to the request message.
See if this approach works for you.
To add Basic Authentication header for your service callout, you can use an 'AssignMessage' policy that sets the 'Authorization' header in the 'GeocodingRequest' as follows:
<AssignMessage enabled="true" continueOnError="true" async="false" name="AssignAuthorizationHeaderPolicy">
<IgnoreUnresolvedVariables>true</IgnoreUnresolvedVariables>
<AssignTo createNew="true" transport="http" type="request">GeocodingRequest</AssignTo>
<Add>
<Headers>
<Header name="Authorization">Basic YourAuthenticationHeader</Header>
</Headers>
</Add>
</AssignMessage>
Once you have created this policy, you will need to attach it in the request flow before the serviceCallout in the proxy.xml as flows:
<Step>
<FaultRules/>
<Name>AssignAuthorizationHeaderPolicy</Name>
</Step>
<Step>
<FaultRules/>
<Name>GeoCodeClient</Name>
</Step>
to add to what's already been said, if you need base64 encoding (and you probably will if you're using Basic Authorization), you'll need to do script callout. For instance, you can use the following Python:
import base64
if (client_secret is not None):
data = client_id + ":" + client_secret
header_value = base64.b64encode(data)
header_value = "Basic " + header_value
flow.setVariable("request.header.Authorization", header_value)
JS will be a little trickier since you need to include appropriate libraries, but I'm sure SO has plenty of more examples to follow for that.
Using Key Value Map to store sensitive data in a secure way
Step 1)Use below API to Create/Update the key Value maphttps://api.enterprise.apigee.com/v1/o/{orgname}/environments/{env}/keyvaluemaps Body:-{
"entry" : [ {
"name" : "basic_auth_system1",
"value" : "Basic XXXXXXXXXXX"
} ],
"name" : "system1_credentials"
}
Step 2) Policy used to lookup The key Value map
<KeyValueMapOperations enabled="true" continueOnError="false" async="false" name="keymap_get_credentials" mapIdentifier="system1_credentials">
<DisplayName>keymap_get_credentials</DisplayName>
<FaultRules/>
<Properties/>
<ExpiryTimeInSecs>-1</ExpiryTimeInSecs>
<Get assignTo="basic_auth_system1">
<Key>
<Parameter>basic_auth_system1</Parameter>
</Key>
</Get>
<Scope>environment</Scope>
</KeyValueMapOperations>

IDataServiceMetadataProvider - Entities dont show up in $metadata

I am trying to write our own RIA services provider to expose data from a server that I access via ODBC. I follow th eguidelines set out at http://blogs.msdn.com/alexj/archive/2010/03/02/creating-a-data-service-provider-part-9-un-typed.aspx
I have written our own IDataServiceMetadataProvider / IDataServiceQueryProvider pair and get no errors on what i do.
I am putting in a resource set like this:
ResourceType tableType = new ResourceType(
typeof(Dictionary<string, object>),
ResourceTypeKind.EntityType,
null,
"Martini",
table_name,
false
);
tableType.CanReflectOnInstanceType = false;
var prodKey = new ResourceProperty(
"Key",
ResourcePropertyKind.Key |
ResourcePropertyKind.Primitive,
ResourceType.GetPrimitiveResourceType(typeof(int))
);
prodKey.CanReflectOnInstanceTypeProperty = false;
tableType.AddProperty(prodKey);
var prodName = new ResourceProperty(
"Name",
ResourcePropertyKind.Primitive,
ResourceType.GetPrimitiveResourceType(typeof(string))
);
prodName.CanReflectOnInstanceTypeProperty = false;
tableType.AddProperty(prodName);
_MetaDataProvider.AddResourceType(tableType);
_MetaDataProvider.AddResourceSet(new ResourceSet(table_name, tableType));
I see the requests coming in for enumerating the resource sets. I check them there in a breakpoint, and the resource set and the type is there, with all properties.
Still, the output I get is:
<?xml version="1.0" encoding="utf-8" standalone="yes" ?>
- <service xml:base="http://localhost:2377/MartiniData.svc/" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:app="http://www.w3.org/2007/app" xmlns="http://www.w3.org/2007/app">
- <workspace>
<atom:title>Default</atom:title>
</workspace>
</service>
And for the $metadata version:
<?xml version="1.0" encoding="utf-8" standalone="yes" ?>
- <edmx:Edmx Version="1.0" xmlns:edmx="http://schemas.microsoft.com/ado/2007/06/edmx">
- <edmx:DataServices xmlns:m="http://schemas.microsoft.com/ado/2007/08/dataservices/metadata" m:DataServiceVersion="1.0">
- <Schema Namespace="Martini" xmlns:d="http://schemas.microsoft.com/ado/2007/08/dataservices" xmlns:m="http://schemas.microsoft.com/ado/2007/08/dataservices/metadata" xmlns="http://schemas.microsoft.com/ado/2007/05/edm">
<EntityContainer Name="Martini" m:IsDefaultEntityContainer="true" />
</Schema>
</edmx:DataServices>
</edmx:Edmx>
The actual metadata for the types never shows up, no error is shown. pretty frustrating. Anyone any idea?
Hmpf. Found.
config.SetEntitySetAccessRule("*", EntitySetRights.All);
was missing in the initialization, so all entities were filtered out ;)

Resources