B-Fabric Web services
Introduction
B-Fabric provides a Web services interface to create, read, update, and delete data in B-Fabric using a programmatic way. For instance, you can create an application which prints a special list of samples (only with attributes which you consider valuable) which belong to a certain project. Therefore, following solution approaches could be considered (list might not be complete):
- You decide not to develop an application, but you hire an employee who creates the list by copying data from the B-Fabric GUI into the document: Well, while this solution approach is mentioned, it is definitely not really considered as such.
- You ask B-Fabric developers to implement this functionality in B-Fabric: At the time of writing, this is the default approach. Thereby, development of B-Fabric may become the bottleneck, since there are not so many developers. Furthermore, in order to make the system running with the new functionality, the system has to be restarted.
- You create an application which takes the csv-file (which can be generated from the GUI) as input, parses it and creates the properly formatted document. This is more realistic, but still implies to much work: every time you want to create such a list, you need to log into B-Fabric, navigate to the correct page, click on the export button, save the file, wait until the download has completed, start your application with the downloaded file as input... yes, go and get a cup of coffee.
- You create an application which connects directly to B-Fabric database and fetches data from there: This is a possibility which is also at least partially utilized, but is still problematic. The business logic is implemented on the application level, not on the database level, so if direct database access is to be granted, then it would be necessary to implement everything on both levels. This is even more complicated in case of performing write operations. Furthermore, connecting to the B-Fabric database would require that a non-standard port would be open (not blocked by the firewall), which is not true if one tries to connect "from outside".
- You create an application which utilizes the web services for fetching the required information from B-Fabric and for creating the list: And the winner is... Well, assume that you have created such an application, which sends an HTTP-request to B-Fabric, receives an HTTP-response and processes it appropriately, then every time you would like to create such a list, you just need to run your application (with the correct project id as input), and it will fetch the most current data from B-Fabric and create the list. You do not have to take care of any limitations caused by firewall filtering since you are using a common protocol (HTTP). Your request is treated in the same as requests from the GUI, the same application logic is utilized.
But what does "sending an HTTP-request" mean? B-Fabric web services utilize
SOAP protocol: the client program creates an appropriately formatted XML content and sends it as an HTTP POST request to B-Fabric, which parses this XML content, creates a new XML content accordingly and sends this as HTTP POST response. The XML schema of the "request XML content" is defined in
WSDL; this information is found in an endpoint, which is simply a URL. This explanation is still too theoretical, so let's have a look at the concrete usage.
HTTP POST requests can be sent in various ways, by utilizing various programming languages. However, for making first steps and getting a feeling of how web services in B-Fabric work, it is highly recommended to use
soapUI, an easy-to-use GUI tool.
After you start it, navigate to File/New soapUI Project. Enter "B-Fabric Demo" for Project Name, and
https://fgcz-bfabric-demo.uzh.ch/bfabric/project?wsdl for Initial WSDL/WADL and click on OK.
This should have created the project "B-Fabric Demo", with one end point (APIProjectPortBinding). In this end point, there is only one web method which can be called, namely "read". Click on the plus symbol to the left of the "read", and double-click on "Request 1"; your soapUI should look as in the following figure.
<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" xmlns:end="https://endpoint.webservice.component.bfabric.org/">
<soapenv:Header/>
<soapenv:Body>
<end:read>
<parameters>
<login>?</login>
<password>?</password>
<!--Optional:-->
<page>?</page>
<!--Optional:-->
<query>
<!--Zero or more repetitions:-->
<createdby>?</createdby>
<!--Zero or more repetitions:-->
<id>?</id>
<!--Zero or more repetitions:-->
<modifiedby>?</modifiedby>
<!--Zero or more repetitions:-->
<name>?</name>
<!--Zero or more repetitions:-->
<projectid>?</projectid>
<!--Zero or more repetitions:-->
<sampleid>?</sampleid>
<!--Zero or more repetitions:-->
<type>?</type>
</query>
</parameters>
</end:read>
</soapenv:Body>
</soapenv:Envelope>
Now edit the xml content so it looks as shown below (replace login/password element values by your own B-Fabric login/password).
request.xml
<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" xmlns:end="https://endpoint.webservice.component.bfabric.org/">
<soapenv:Header/>
<soapenv:Body>
<end:read>
<parameters>
<login>bemployee</login>
<password>fakedpw</password>
<query>
<id>403</id>
</query>
</parameters>
</end:read>
</soapenv:Body>
</soapenv:Envelope>
Click on the button with the green arrow in the left upper corner of the "Request 1" window, as shown in the following figure, then some XML text should appear in the right part of the window. If this is the case, then you have just successfully sent a request to the web service endpoint and received the response. This is all about the mechanism of the web service communication: you create properly formatted XML text used as request, send it to the appropriate end point, and then you receive an XML text as response.
If this didn't work, then check the following points:
- You didn't type your username/password correctly, or you do not have the employee/feeder role (the role "user" is not sufficient).
- The XML request text is not formatted correctly.
Web Service Programming
soapUI is a nice graphical tool; but wasn't the idea of web services to allow applications to communicate with B-Fabric? soapUI is considered only for getting an impression of how web services work, but it is not thought to be used for programming other tools. As already mentioned, the whole communication is performed by sending HTTP POST requests and receiving HTTP POST responses, so one only needs to find out how this is to be done with a particular programming language. There might be several ways to do this; in the following sections, some possible ways are shown.
With the current architecture, when the API must be extended, the following classes should be either created or modified. For example if the api for the consumable must be available at at url
https://fgcz-bfabric.uzh.ch/bfabric/consumable?wsdl then we have to create the following files:
- forms/MFConsumable.java
- webservice/client/SoapClient.java
- webservice/client/endpoint/EPConsumable.java
- webservice/client/request/SoapRequestRead.java
- webservice/client/request/SoapRequestSave.java
- webservice/request/read/parameters/XMLRequestReadParametersConsumable.java
- webservice/request/read/parameters/query/XMLRequestReadQueryConsumable.java
- webservice/request/save/parameters/XMLRequestSaveParametersConsumable.java
- webservice/request/save/parameters/entity/XMLRequestSaveConsumable.java
- webservice/response/XMLResponse.java
- webservice/server/endpoint/APIConsumable.java
- webservice/server/manager/WSConsumableManager.java
- xml/entity/XMLConsumable.java
Note that you can list all web service by just using the name of endpoint, e.g.:
https://fgcz-bfabric.uzh.ch/bfabric/consumable
For bash, consider using the tool
curl. You can either place the content of the XML request text in a file, or you can let curl read it from STDIN. Create a text file and put the same XML content into it which you used for testing soapUI (let's say that the name of this file is request.xml). Now run the following command:
curl reading request from the file
curl -L --header "Content-Type:text/xml;charset=UTF-8" -d @request.xml https://fgcz-bfabric-demo.uzh.ch/bfabric/project?wsdl -s -S
Now use the tool "cat" for printing the content of the xml file to standard output, redirect it to standard input and run curl:
curl reading request content from STDIN
cat request.xml | curl -L --header "Content-Type:text/xml;charset=UTF-8" -d @- https://fgcz-bfabric-demo.uzh.ch/bfabric/project?wsdl -s -S
You may notice that now we did not specify the file name after the @, but just a minus. This tells curl to read the xml request content from STDIN. Independent of the way how you run this, the produced output text is placed into one single line. While this is perfectly readable for a computer, it is very difficult for a person to use this (if necessary). Therefore, one should consider installing the command line tool xmlstarlet and let it format the output.
with formatting
cat request.xml | curl -L --header "Content-Type:text/xml;charset=UTF-8" -d @- https://fgcz-bfabric-demo.uzh.ch/bfabric/project?wsdl -s -S | xmlstarlet fo
For parsing, consider using
xmlstarlet or
xsltproc. More information about the XML schema can be found in the section "Available Endpoints".
#!/usr/bin/perl
# ---------------------------------------------------------------------
# webservice.pl
# ---------------------------------------------------------------------
use LWP::UserAgent;
use HTTP::Request::Common;
use XML::Simple;
sub XMLreadOneEntityObject($$) {
my $entity = shift;
my $entityid = shift;
my $xml = new XML::Simple;
my $userAgent = LWP::UserAgent->new(agent => Config::getWebAgent());
my $messageheader = "<soapenv:Envelope xmlns:soapenv=\"https://schemas.xmlsoap.org/soap/envelope/\" xmlns:end=\"https://endpoint.webservice.component.bfabric.org/\"> <soapenv:Header/> <soapenv:Body> <end:read>";
my $messagefooter = "</end:read> </soapenv:Body> </env:Envelope>";
my $parameter="<parameters></parameters>";
my $query="<query></query>";
my $xmldata = $xml->XMLin($parameter);
$xmldata->{login}=Config::getSoapLogin();
$xmldata->{password}=Config::getSoapPasswd();
my $xmlquery = $xml->XMLin($query);
$xmlquery->{id}=$entityid;
$xmldata->{query}=$xmlquery;
my $message="$messageheader".$xml->XMLout($xmldata,NoAttr => 1,RootName => 'parameters')."$messagefooter";
my $response = $userAgent->request(POST Config::getBfabricWSDL($entity),
Content_Type => 'text/xml',
Content => $message);
if (! $response->is_success ) {
die;
}
my $data = $xml->XMLin($response->decoded_content, KeyAttr => [ 'id' ] , ForceArray => [ 'id','role','project' ] );
$data= $data->{'S:Body'}->{'ns2:readResponse'}->{'return'}->{$entity};
return $data;
}
[+] Python
There are several Python Packages around helping to work with SOAP. Some tests are done with the following:
- zeep
- forks of suds: suds-community, suds-py3
- https://github.com/fgcz/bfabricPy: This package connects the bfabric system to the python and R world while providing a JSON and REST interface using Flask. The bfabricShiny R package is an extension and provides code snippets and sample implementation for a seamless R shiny bfabric integration. For more advanced users the bfabricPy package also provides a powerful query interface on the command-line though using the provided scripts.
#!/usr/bin/python
import os
import re
import sys
import time
import datetime
from suds.client import Client
wsdlurl='https://fgcz-bfabric.uzh.ch/bfabric/importresource?wsdl'
client=Client(wsdlurl)
BFLOGIN='pfeeder'
BFPASSWORD='secret'
def submit(line):
QUERY=dict(login=BFLOGIN,
password=BFPASSWORD,
importresource=dict(applicationid=myapplicationid,
filechecksum=myfilechecksum,
projectid=myprojectid,
filedate=myfiledate,
relativepath=myrelativepath,
size=mysize,
storageid=mystorageid)
)
res=client.service.save(QUERY)
As in SoapUI, you build your SoapRequest and send it to an SoapEndpoint. The following Java program demonstrates how to call a web service on an endpoint in the most generic way. In this example we call the read web service on the workunit endpoint:
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import javax.xml.soap.MessageFactory;
import javax.xml.soap.SOAPConnection;
import javax.xml.soap.SOAPConnectionFactory;
import javax.xml.soap.SOAPException;
import javax.xml.soap.SOAPMessage;
public class SOAPCall {
public static void main(String[] args) throws UnsupportedOperationException, SOAPException, IOException {
// Note: The name space end has a different URL in B-Fabric 10!
String soapString = "<soapenv:Envelope xmlns:soapenv=\"https://schemas.xmlsoap.org/soap/envelope/\" xmlns:end=\"https://endpoint.server.webservice.bfabric.org/\"><soapenv:Header/><soapenv:Body><end:read><parameters><login>bemployee</login><password>0f81754c2f9e0993700ad483b7ee4447</password><query><id>123456</id></query></parameters></end:read></soapenv:Body></soapenv:Envelope>";
String endPoint = "https://fgcz-bfabric-test.uzh.ch/bfabric/workunit";
// String soapString = "<soapenv:Envelope xmlns:soapenv=\"https://schemas.xmlsoap.org/soap/envelope/\" xmlns:end=\"https://endpoint.webservice.component.bfabric.org/\"><soapenv:Header/><soapenv:Body><end:read><parameters><login>bemployee</login><password>fakepw</password><query><id>123456</id></query></parameters></end:read></soapenv:Body></soapenv:Envelope>";
// String endPoint = "https://fgcz-bfabric-demo.uzh.ch/bfabric/workunit";
SOAPConnectionFactory soapConnectionFactory = SOAPConnectionFactory.newInstance();
SOAPConnection soapConnection = soapConnectionFactory.createConnection();
InputStream is = new ByteArrayInputStream(soapString.getBytes());
SOAPMessage request = MessageFactory.newInstance().createMessage(null, is);
SOAPMessage response = soapConnection.call(request, endPoint);
System.out.println("SOAP request");
request.writeTo(System.out);
System.out.println("");
System.out.println("SOAP response");
response.writeTo(System.out);
System.out.println("");
}
}
You can also use the wsimport tool to parse an existing Web Services Description Language (WSDL) file and generate required files (JAX-WS portable artifacts) for web service client to access the published web services. This wsimport tool is available in the $JDK/bin folder. Example usage:
wsimport -keep -verbose https://fgcz-bfabric-test.uzh.ch/bfabric/workunit?wsdl
You can then use the generated classes as follows:
package org.bfabric.webservice.server.endpoint;
import java.util.ArrayList;
import java.util.List;
public class WorkunitTest {
public static void main(String[] args) {
Workunit workunit = new Workunit();
APIWorkunit apiWorkunitPort = workunit.getAPIWorkunitPort();
XmlRequestReadParametersWorkunit parameters = new XmlRequestReadParametersWorkunit();
parameters.setLogin("bemployee");
parameters.setPassword("fakepw");
XmlRequestReadQueryWorkunit query = new XmlRequestReadQueryWorkunit();
List<Long> workunitidList = new ArrayList<>();
workunitidList.add(123456L);
query.id = workunitidList;
parameters.setQuery(query);
List<XmlWorkunit> value = apiWorkunitPort.read(parameters).getWorkunit();
for (XmlWorkunit xmlWorkunit : value) {
System.out.println(xmlWorkunit.getStatus());
}
}
}
if you have the B-Fabric jar file as library, you may go the following way:
If you have not installed the
Eclipse development environment yet, then please do it as first step. Afterwards, create a new project (let's say wstest), right click on it and navigate to Build Path/Configure Build Path.
Click on the tab "Libraries", and then on the button "Add External JARs...". Select the downloaded bfabric.jar file and finally click on OK. The jar-file is now among referenced libraries.
Now create a class "WsTest" and make sure that it contains the following code (make sure that the hostname/username/password are correct):
import org.bfabric.webservice.client.SoapClient;
import org.bfabric.xml.entity.XMLProject;
public class WsTest {
public static void main(String[] args) {
SoapClient soapClient = new SoapClient("fgcz-bfabric-demo.uzh.ch", "bemployee", "fakepw");
// If you want to access B-Fabric on your localhost, use the following constructor instead of the above one:
// SoapClient soapClient = new SoapClient("localhost", 8080, "bemployee", "fakepw");
// getEpProject() stands for "get endpoint project", getWmRead() for "get web method read"
XMLProject project = soapClient.getEpProject().getWmRead().getEntity(403);
System.out.println(project);
}
}
This should print now the same XML text as previously when using soapUI resp curl. The advantage of using this software is the fact that the "unmarshalling", that means the parsing of the XML text and creation of POJOs (plain old java objects) is already implemented. Furthermore, the project object might be connected to different other entities, like samples. Assume that we want to print a list of "ID, Name" of the samples, then the code would look as follows:
import org.bfabric.webservice.client.SoapClient;
import org.bfabric.xml.entity.XMLProject;
public class WsTest {
public static void main(String[] args) {
SoapClient soapClient = new SoapClient("fgcz-bfabric-demo.uzh.ch", "bemployee", "fakepw");
// If you want to access B-Fabric on your localhost, use the following constructor instead of the above one:
// SoapClient soapClient = new SoapClient("localhost", 8080, "bemployee", "fakepw");
// getEpProject() stands for "get endpoint project", getWmRead() for "get web method read"
XMLProject project = soapClient.getEpProject().getWmRead().getEntity(403);
for (XMLSample xmlSample : project.getSamples()) {
System.out.println(xmlSample.getId() + ", " + xmlSample.getName());
}
}
}
The first argument for the SoapClient constructor is the hostname (also the IP can be used), the second the B-Fabric login and the third the corresponding password.When fetching the entity "project", information about sample is not delivered by B-Fabric; the same soapClient object which was used for fetching the project entity is now used for connecting to B-Fabric a second time and for fetching the sample information. This happens in a "lazy" manner (means that samples are not loaded when executing
XMLProject project = soapClient.getEpProject().getWmRead().getEntity(403);, they are loaded after calling
project.getSamples().
[+] B-Fabric Shiny
Available Web Services
Currently, all endpoints provide methods in connection to some B-Fabric entity.
All searches will return a maximum of 100 entities per page. If all entities are needed a loop over the pages is needed.
Endpoints are available for following entities (in development, list not complete):
In order to use provided web services, you must be granted with at least one of these roles: admin, feeder or application manager.
Note: In you want to perform a contains instead of an exact match in case of string values, please use the percentage symbol around the value. Example:
<description>%rat%</description>