Using the BI Server Metadata Web Service for Automated RPD Modifications
A little-known new feature of OBIEE 11g is a web service interface to the BI Server. Called the “BI Server Metadata Web Service” it gives a route into making calls into the BI Server using SOAP-based web services calls. Why is this useful? Because it means you can make any call to the BI Server (such as SAPurgeAllCache) from any machine without needing to install any OBIEE-related artefacts such as nqcmd, JDBC drivers, etc. The IT world has been evolving over the past decade or more towards a more service-based architecture (remember the fuss about SOA?) where we can piece together the functionality we need rather than having one monolithic black box trying to do everything. Being able to make use of this approach in our BI deployments is a really good thing. We can do simple things like BI Server cache management using a web service call, but we can also do more funky things, such as actually updating repository variable values in real time - and we can do it from within our ETL jobs, as part of an automated deployment script, and so on.
Calling the BI Server Metadata Web Service
First off, let’s get the web service configured and working. The documentation for the BI Server Metadata Web Service can be found here, and it’s important to read it if you’re planning on using this. What I describe here is the basic way to get it up and running.
Configuring Security
We need to configure the security against the web service to define what kind of authentication is required by it to use. If you don’t do this, you won’t be able to make calls to it. Setting up the security is a case of attaching a security policy in WebLogic Server to the web service (called AdminService) itself. I’ve used oracle/wss_http_token_service_policy which means that the credentials can be passed through using standard HTTP Basic authentication.
You can do this through Enterprise Manager:
Or you can do it through WLST using the attachWebServicePolicy call.
You also need to configure WSM, adding some entries to the credential store as detailed here.
Testing the Web Service
The great thing about web services is that they can be used from pretty much anywhere. Many software languages will have libraries for making SOAP calls, and if they don’t, it’s just HTTP under the covers so you can brew your own. For my testing I’m using the free version of SoapUI. In anger, I’d use something like curl
for a standalone script or hook it up to ODI for integration in the batch.
Let’s fire up SoapUI and create a new project. Web services provide a Web Service Definition Language (WSDL) that describes what and how they work, which is pretty handy. We can pass this WSDL to SoapUI for it to automatically build some sample requests for us. The WSDL for this web service is
http://<biserver>:<port>/AdminService/AdminService?WSDL
Where biserver is your biserver (duh) and port is the managed server port (usually 9704, or 7780).
We’ve now got a short but sweet list of the methods we can invoke:
Expand out callProcedureWithResults and double click on Request 1. This is a template SOAP message that SoapUI has created for you, based on the WSDL.
Edit the XML to remove the parameters section, and just to test things out in procedureName put GetOBISVersion(). Your XML request should look like this:
<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" xmlns:ws="http://ws.admin.obiee.oracle/"> <soapenv:Header/> <soapenv:Body> <ws:callProcedureWithResults> <procedureName>GetOBISVersion()</procedureName> </ws:callProcedureWithResults> </soapenv:Body> </soapenv:Envelope>
If you try and run this now (green arrow or Cmd-Enter on the Mac) you’ll see the exact same XML appear on the right pane, which is strange… but click on Raw and you’ll see what the problem is :
Remember the security stuff we set up on the server previously? Well as the client we now need to keep our side of the bargain, and pass across our authentication with the SOAP call. Under the covers this is a case of sending HTTP Basic auth (since we’re using oracle/wss_http_token_service_policy
), and how you do this depends on how you’re making the call to the web service. In SoapUI you click on the Auth button at the bottom of the screen, Add New Authentication, Type: Basic, and then put your OBIEE server username/password in.
Now you can click submit on the request again, and you should see in the response pane (on the right) in the XML view details of your BI Server version (you may have to scroll to the right to see it)
This simple test is just about validating the end-to-end calling of a BI Server procedure from a web service. Now we can get funky with it….
Updating Repository Variables Programatically
Repository variables in OBIEE are “global”, in that every user session sees the same value. They’re often used for holding things like “what was the last close of business date”, or “when was the data last updated in the data warehouse”. Traditionally these have been defined as dynamic repository variables with an initialisation block that polled a database query on a predefined schedule to get the current value. This meant that to have a variable showing when your data was last loaded you’d need to (a) get your ETL to update a row in a database table with a timestamp and then (b) write an init block to poll that table to get the value. That polling of the table would have to be as frequent as you needed in order to show the correct value, so maybe every minute. It’s kinda messy, but it’s all we had. Here I’d like to show an alternative approach.
Let’s say we have a repository variable, LAST_DW_REFRESH. It’s a timestamp that we use in report predicates and titles so that when they are run we can show the correct data based on when the data was last loaded into the warehouse. Here’s a rather over-simplified example:
The Title view uses the code:
Data correct as of: @{biServer.variables['LAST_DW_REFRESH']}
Note that we’re referencing the variable here. We could also put it in the Filter clause of the analysis. Somewhat tenuously, let’s imagine we have a near-realtime system that we’re federating a DW across direct from OLTP, and we just want to show data from the last load into the DW:
For the purpose of this example, the report is less important than the diagnostics it gives us when run, in nqquery.log. First we see the inbound logical request:
-------------------- SQL Request, logical request hash: 45f9492a SET VARIABLE QUERY_SRC_CD='Report',PREFERRED_CURRENCY='USD';SELECT 0 s_0, "A - Sample Sales"."Time"."T05 Per Name Year" s_1, "A - Sample Sales"."Base Facts"."1- Revenue" s_2 FROM "A - Sample Sales" WHERE ("Time"."T00 Calendar Date" < VALUEOF("LAST_DW_REFRESH")) ORDER BY 1, 2 ASC NULLS LAST FETCH FIRST 5000001 ROWS ONLY
Note the VALUEOF clause. When this is parsed out we get to see the actual value of the repository variable that OBIEE is going to execute the query with:
-------------------- Logical Request (before navigation): [[ RqList 0 as c1 GB, D0 Time.T05 Per Name Year as c2 GB, 1- Revenue:[DAggr(F0 Sales Base Measures.1- Revenue by [ D0 Time.T05 Per Name Year] )] as c3 GB DetailFilter: D0 Time.T00 Calendar Date < TIMESTAMP '2015-09-24 23:30:00.000' OrderBy: c1 asc, c2 asc NULLS LAST
We can see the value through the Administration Tool Manage Sessions page too, but it’s less convenient for tracking in testing:
If we update the RPD online with the Administration Tool (nothing fancy at this stage) to change the value of this static repository variable :
And then rerun the report, we can see the value has changed:
-------------------- Logical Request (before navigation): [[ RqList 0 as c1 GB, D0 Time.T05 Per Name Year as c2 GB, 1- Revenue:[DAggr(F0 Sales Base Measures.1- Revenue by [ D0 Time.T05 Per Name Year] )] as c3 GB DetailFilter: D0 Time.T00 Calendar Date < TIMESTAMP '2015-09-25 00:30:00.000' OrderBy: c1 asc, c2 asc NULLS LAST
Now let’s do this programatically. First off, the easy stuff, that’s been written about plenty before. Using biserverxmlgen
we can create a XUDML version of the repository. Searching through this we can pull out the variable definition:
<Variable name="LAST_DW_REFRESH" id="3031:286125" uid="00000000-1604-1581-9cdc-7f0000010000"> <Expr><![CDATA[TIMESTAMP '2015-09-25 00:30:00']]></Expr> </Variable>
and then wrap it in the correct XML structure and update the timestamp we want to use:
<?xml version="1.0" encoding="UTF-8" ?> <Repository xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"> <DECLARE> <Variable name="LAST_DW_REFRESH" id="3031:286125" uid="00000000-1604-1581-9cdc-7f0000010000"> <Expr><![CDATA[TIMESTAMP '2015-09-26 00:30:00']]></Expr> </Variable> </DECLARE> </Repository>
(you can also get this automagically by modifying the variable in the RPD, saving the RPD file, and then comparing it to the previous copy to generate a patch file with the Administration Tool or comparerpd
)
Save this XML snippet, with the updated timestamp value, as last_dw_refresh.xml
. Now to update the value on the BI Server in-flight, first using the OBIEE tool biserverxmlcli
. This is available on all OBIEE servers and client installations - we’ll get to web services for making this update call remotely in a moment.
biserverxmlcli -D AnalyticsWeb -R Admin123 -U weblogic -P Admin123 -I last_dw_refresh.xml
Here -D is the BI Server DSN, -U / -P are the username/password credentials for the server, and the -R is the RPD password.
Running the analysis again shows that it is now working with the new value of the variable:
-------------------- Logical Request (before navigation): [[ RqList 0 as c1 GB, D0 Time.T05 Per Name Year as c2 GB, 1- Revenue:[DAggr(F0 Sales Base Measures.1- Revenue by [ D0 Time.T05 Per Name Year] )] as c3 GB DetailFilter: D0 Time.T00 Calendar Date < TIMESTAMP '2015-09-26 00:30:00.000' OrderBy: c1 asc, c2 asc NULLS LAST
Getting Funky – Updating RPD from Web Service
Let’s now bring these two things together - RPD updates (in this case to update a variable value, but could be anything), and BI Server calls.
In the above web service example I called the very simple GetOBISVersion
. Now we’re going to use the slightly more complex NQSModifyMetadata
. This is actually documented, and what we’re going to do is pass across the same XUDML that we sent to biserverxmlcli
above, but through the web service. As a side note, you could also do this over JDBC if you wanted (under the covers, AdminService is just a web app with a JDBC connector to the BI Server).
I’m going to do this in SoapUI here for clarity but I actually used SUDS to prototype it and figure out the exact usage.
So as a quick recap, this is what we’re going to do:
- Update the value of a repository variable, using XUDML. We generated this XUDML from
biserverxmlgen
and wrapped it in the correct XML structure.
We could also have got it throughcomparerpd
or the Administration Tool ‘create patch’ function - Use the BI Server’s NQSModifyMetadata call to push the XUDML to the BI Server online.
We saw thatbiserverxmlcli
can also be used as an alternative to this for making online updates through XUDML. - Use the BI Server Metadata Web Service (AdminService) to invoke the NQSModifyMetadata call on the BI Server, using the callProcedureWithResults method
In SoapUI create a new request:
Set up the authentication:
Edit the XML SOAP message to remove parameters and specify the basic BI Server call:
<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" xmlns:ws="http://ws.admin.obiee.oracle/"> <soapenv:Header/> <soapenv:Body> <ws:callProcedureWithResults> <procedureName>NQSModifyMetadata()</procedureName> </ws:callProcedureWithResults> </soapenv:Body> </soapenv:Envelope>
Now the tricky bit - we need to cram the XUDML into our XML SOAP message. But, XUDML is its own kind of XML, and all sorts of grim things happen here if we’re not careful (because the XUDML gets swallowed up into the SOAP message, it all being XML). The solution I came up with (which may not be optimal…) is to encode all of the HTML entities, which a tool like this does (and if you’re using a client library like SUDS will happen automagically). So our XUDML, with another new timestamp for testing:
<?xml version="1.0" encoding="UTF-8" ?> <Repository xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"> <DECLARE> <Variable name="LAST_DW_REFRESH" id="3031:286125" uid="00000000-1604-1581-9cdc-7f0000010000"> <Expr><![CDATA[TIMESTAMP '2015-09-21 00:30:00']]></Expr> </Variable> </DECLARE> </Repository>
becomes:
<?xml version="1.0" encoding="UTF-8" ?> <Repository xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"> <DECLARE> <Variable name="LAST_DW_REFRESH" id="3031:286125" uid="00000000-1604-1581-9cdc-7f0000010000"> <Expr><![CDATA[TIMESTAMP '2015-09-21 00:30:00']]></Expr> </Variable> </DECLARE> </Repository>
We’re not quite finished yet. Because this is actually a call (NQSModifyMetadata) nested in a call (callProcedureWithResults) we need to make sure NQSModifyMetadata gets the arguments (the XUDML chunk) through intact, so we wrap it in single quotes - which also need encoding ('
):
'<?xml version="1.0" encoding="UTF-8" ?> <Repository xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"> <DECLARE> <Variable name="LAST_DW_REFRESH" id="3031:286125" uid="00000000-1604-1581-9cdc-7f0000010000"> <Expr><![CDATA[TIMESTAMP '2015-09-21 23:30:00']]></Expr> </Variable> </DECLARE> </Repository>'
and then for final good measure, the single quotes around the timestamp need double-single quoting:
'<?xml version="1.0" encoding="UTF-8" ?> <Repository xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"> <DECLARE> <Variable name="LAST_DW_REFRESH" id="3031:286125" uid="00000000-1604-1581-9cdc-7f0000010000"> <Expr><![CDATA[TIMESTAMP ''2015-09-21 23:30:00'']]></Expr> </Variable> </DECLARE> </Repository>'
Nice, huh? The WSDL suggests that parameters for these calls should be able to be placed within the XML message as additional entities, which I wonder if would allow for proper encoding, but I couldn’t get it to work (I kept getting java.sql.SQLException: Parameter 1 is not bound).
So, stick this mess of encoding plus twiddles into your SOAP message and it should look like this: (watch out for line breaks; these can break things)
<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" xmlns:ws="http://ws.admin.obiee.oracle/"> <soapenv:Header/> <soapenv:Body> <ws:callProcedureWithResults> <procedureName>NQSModifyMetadata('<?xml version="1.0" encoding="UTF-8" ?> <Repository xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"> <DECLARE> <Variable name="LAST_DW_REFRESH" id="3031:286125" uid="00000000-1604-1581-9cdc-7f0000010000"> <Expr><![CDATA[TIMESTAMP ''2015-09-21 23:30:00'']]></Expr> </Variable> </DECLARE> </Repository>')</procedureName> </ws:callProcedureWithResults> </soapenv:Body> </soapenv:Envelope>
Hit run, and with a bit of luck you’ll get a “nothing to report” response :
If you look in nqquery.log you’ll see:
[...] NQSModifyMetadata started. [...] NQSModifyMetadata finished successfully.
and all-importantly when you run your report, the updated variable will be used:
-------------------- Logical Request (before navigation): [[ RqList 0 as c1 GB, D0 Time.T05 Per Name Year as c2 GB, 1- Revenue:[DAggr(F0 Sales Base Measures.1- Revenue by [ D0 Time.T05 Per Name Year] )] as c3 GB DetailFilter: D0 Time.T00 Calendar Date < TIMESTAMP '2015-09-21 23:30:00.000' OrderBy: c1 asc, c2 asc NULLS LAST
If this doesn’t work … well, best of luck. Use nqquery.log, bi_server1.log (where the AdminService writes some diagnostics) to try and trace the issue. Also test calling NQSModifyMetadata from JDBC (or ODBC) directly, and then add in the additional layer of the web service call.
So, in the words of Mr Rittman, there you have it. Programatically updating the value of repository variables, or anything else, done online, and for bonus points done through a web service call making it possible to use without any local OBIEE client/server tools.