Petroware Logo

Tutorial I: Using JWitsml with ETP

ETP communication may be simple in theory, but when creating full-scale enterprise-ready 24/7-reliable real-time software, ETP soon becomes difficult to handle. The goal of the JWitsml project is to encapsulate all the complexity of ETP and provide a clean, simple and well documented API directly towards the back-end business model.

Energistics provides various development kits for ETP. One should be careful when using these as they expose far more of the ETP and web socket logic than necessary. They can act as useful starting blocks, but the elements of these kits are not equipped to be part of an enterprise application data model.

ETP communication is done through predefined Avro encoded messages and data structures. The data structures contains bulk data in compressed XML form according to the WITSML version being used. All web socket communication is asynchronous. On top of the web socket protocol ETP defines a separate session model. Through its nine sub protocols ETP defines 54 different messages that can be sent between a server and a client. JWitsml has simplified this logic, and achieve full ETP coverage through only eleven simple methods. The result is a very narrow and simple to use API.

All ETP communication starts by creating an EtpServer instance that represents the ETP server in the client program:

      
      // Create ETP server endpoint
      EtpServer etpServer = new EtpServer("wss://path/to/etp/server", "userName", "password");
    

The EtpServer is a factory for EtpSession instances that implements the main methods of the ETP protocols. The EtpSession is of read or write type depending on the ETP method that is to be called.

      
      // Create an ETP read session
      EtpSession etpSession = etpServer.newReadSession();
      :
    

The nine different sub protocols of ETP each defines a specific appliance of ETP. Each of these, and how they are accessed through JWitsml are discussed in detail below. In this discussion it is important to realize that due to the nature of web socket communication most calls to EtpSession will be multi-threaded in the back-end. This complexity is transparent for the JWitsml client, but as communication may both hang or fail it is important that client code always access EtpSession outside the main application thread.

Protocol 0 - Core

The Core protocol defines several messages that manage ETP sessions. None of these are exposed to the JWitsml client; The back-end ETP sessions are automatically established when required and re-established when lost. In addition, protocol roles are automatically negotiated according to the nature of the client calls.

Protocol 3 - Discovery

The Discovery protocol is used to navigate the remote store. Instances in the store are organized in a strict hierarchy, and each item is identified by a resource. The resources are easily navigated by sending GetResources messages to the ETP session:

      
      // Get children resources for a given parent (null for root level)
      List<EtpResource> resources = etpSession.getResources(parentResource);
    

This simple method is the typical basis for a GUI tree component that exposes the entire structure of a remote store.

Protocol 1 - Channel streaming

ETP log data are streamed by channel but are in JWitsml organized as log sets to be more consistent with industry well logging standards. A log set has an index curve and a number of value curves sampled along the index.

A log set is established by sending the ChannelDescribe message to the ETP server:

      
      // Get log set for a specific log, curve, or channel set resource
      EtpLogSet logSet = etpSession.channelDescribe(resource);
    
This log set will have no values, but will have the index and all its curve definitions in place.

Log set population is controlled through the ChannelStreamingStart and the ChannelStreamingStop messages:

       
       // Start channel streaming to the given log set from the given index value or (if null) from top
       etpSession.channelStreamingStart(logSet, indexValue);
       
       // Stop streaming
       etpSession.channelStreamingStop(logSet);

    

The EtpLogSet class is thread-safe, secured by separate read- and write locks in order to ensure protection and maximum performance for the client code.

The client listens for changes to the log set by adding an EtpLogSetListener to the log set:

      
      // Listening interface for log set changes
      public interface EtpLogSetListener
      {
        void logSetChanged(EtpLogSet logSet);
      }
    

The typical logSetChanged response for the client will be to refresh a GUI.

Protocol 2 - Channel Data Frame

The Channel Data Frame protocol exists in order to be able to retrieve complete log sets of historic (non-streaming) data. This can easily be achieved through the protocol 1 approach, but it is even simpler using protocol 2; There is no need to listen for log set changes as the log set is populated on return:

      
      // Get log set for a specific log, curve, or channel set resource
      EtpLogSet logSet = etpSession.requestChannelData(resource, fromIndex, toIndex);
    
Again: The implementation of the method is multi-threaded, and it blocks until the process is complete. It should consequently not be called from a main application thread.

Protocol 4 - Store

The Store protocol is for CRUD access to store instances. ETP objects are communicated through Avro objects with embedded XML in compressed form. All this complexity is transparent to the JWitsml client however as the XML are conveniently uncompressed and converted into their equivalent Java classes as defined for each WITSML version 1.3, 1.4 or 2.0.

      
      // Get object from data store
      WitsmlObject witsmlObject = etpSession.getObject(resource);
      
      // Create or update an object in data store
      etpSession.putObject(witsmlObject);
      
      // Delete object from data store
      etpSession.deleteObject(witsmlObject);
    

Protocol 5 - Store notification

The Store Notification protocol is used to allow client programs to receive notifications of changes to data objects in the store. Notification subscription is done by sending a NotificationRequest message to the ETP server:

      
      // Get notifications on the object identified by the specified resource
      etpSession.notificationRequest(resource, listener);
      
      // Cancel notifications on the object
      etpSession.cancelNotification(resource);
    

The listening interface is defined as follows:

      public interface EtpStoreNotificationListener {
        
        // Indicate that an object has been changed
        void objectChanged(EtpResource resource);
        
        // Indicate that an object has been deleted
        void objectDeleted(EtpResource resource);
      }
    

Protocol 6 - Growing Object

The Growing Object protocol is used to manage non-log index-based objects: trajectories, wellbore geology intervals and wellbore geometry sections. For some reason this protocol is not streaming, but responds to explicit calls from the client.

The growing object protocol defines several methods for accessing growing objects, but due to the push/pull nature of the calls JWitsml exposes only one of these, the GrowingObjectGetRange, conveniently renamed to growingObjectGet and always getting the full range of values:

      
      // Get the specified growing object
      WitsmlObject witsmlObject = etpSession.growingObjectGet(resource);
    

Protocol 7 - Data Array

The Data Array protocol is used to transfer large, binary arrays of data between a client and an ETP server. The protocol is not used by WITSML, but as ETP is the foundation for PRODML and RESQML as well, it is included in the ETP module of JWistml for completeness:

      
      // Get a specific data array from the server
      EtpDataArray dataArray = etpSession.getDataArray(uri);
      
      // Get part of a data array from the server
      EtpDataArray dataArray = etpSession.getDataArraySlice(uri, start, count);
      
      // Send a data array to the server
      etpSession.putDataArray(uri, dataArray);
      
      // Send part of a data array to the server
      etpSession.putDataArraySlice(uri, dataArray, start, count););
    

Protocol 8 - WITSML SOAP

The WITSML SOAP protocol allows ETP clients to make WITSML 1.* server calls using ETP instead of SOAP. This can be thought of as a technology migration, but it is not included in JWitsml as JWitsml contains both the SOAP and the ETP technologies anyway.



Tutorial II: Using JWitsml with HTTP/SOAP

Below is a complete example showing the few simple steps necessary to access data from a WITSML SOAP based server. The program lists the names of all wells in a given server.

      import java.net.MalformedURLException;
      import java.net.URL;
      import java.util.List;

      import no.petroware.jwitsml.Capabilities;
      import no.petroware.jwitsml.WitsmlQuery;
      import no.petroware.jwitsml.WitsmlServer;
      import no.petroware.jwitsml.WitsmlServerException;
      import no.petroware.jwitsml.WitsmlVersion;
      import no.petroware.jwitsml.model.WitsmlWell;

      public class BasicExample {
        public static void main(String[] arguments) {
          try {
            // Identify ourself and our capabilities as WITSML client
            Capabilities clientCapabilities = new Capabilities(WitsmlVersion.VERSION_1_4,
                                                               "First And Last name",
                                                               "e-mail@some.org",
                                                               "+12 34 56 789",
                                                               "Description",
                                                               "Application Name",
                                                               "Vendor",
                                                               "Version Number");
            
            // Establish URL to the server
            URL url = new URL("http://path/to/witsml/server");
            
            // Create the WITSML server instance
            WitsmlServer witsmlServer = new WitsmlServer(url, "userName", "password",
                                                         clientCapabilities);
            
            // Retrieve all wells
            List<WitsmlWell> wells = witsmlServer.get(WitsmlWell.class,
                                                      new WitsmlQuery());
            
            // Write well names to the console
            for (WitsmlWell well : wells)
              System.out.println(well.getName());
          }
          catch (MalformedURLException exception) {
            exception.printStackTrace();
          }
          catch (WitsmlServerException exception) {
            exception.printStackTrace();
          }
        }
      }
    

Unlike the previous ETP examples, all calls to the WitsmlServer instance are single-threaded.

Getting WITSML data from the server

All data are retrieved through a WitsmlServer instance which represents the WITSML server in the client program.

There are bascially three different ways to get data:

1. Get multiple children of a given type from a specific parent

     
     // Get all wellbores from a given well instance
     List<WitsmlWellbore> wellbores = witsmlServer.get(WitsmlWellbore.class,
                                                       new WitsmlQuery(),
                                                       well);

     
     // Get all rigs from a given wellbore instance
     List<WitsmlRig> rigs = witsmlServer.get(WitsmlRig.class,
                                             new WitsmlQuery(),
                                             wellbore);

     
     // ... or through the wellbore ID
     List<WitsmlRig> rigs = witsmlServer.get(WitsmlRig.class,
                                             new WitsmlQuery(),
                                             "ID-wellbore");

     
     // Both parent and grandparent may be specified
     List<WitsmlMudLog> mudLogs = witsmlServer.get(WitsmlMudLog.class,
                                                   new WitsmlQuery(),
                                                   "ID-wellbore",
                                                   "ID-well");

     
     // Wells doesn't have parents so we can skip the parent argument
     List<WitsmlWell> wells = witsmlServer.get(WitsmlWell.class,
                                               new WitsmlQuery());

     
     // Attempt to get all rigs! Some servers will accept this approach
     // while others will require a valid wellbore parent.
     // The WITSML standard is unclear on this issue.
     List<WitsmlRig> rigs = witsmlServer.get(WitsmlRig.class,
                                             new WitsmlQuery());

  

2. Get a specific child of a given type from a specific parent

     
     // Get a specific log instance from a wellbore instance
     WitsmlLog log = witsmlServer.getOne(WitsmlLog.class,
                                         new WitsmlQuery(),
                                         "ID-log",
                                         wellbore);

     
     // ... or through the wellbore ID
     WitsmlLog log = witsmlServer.getOne(WitsmlLog.class,
                                         new WitsmlQuery(),
                                         "ID-log",
                                         "ID-wellbore");

     
     // Wells doesn't have parents so we can skip the parent argument
     WitsmlWell well = witsmlServer.getOne(WitsmlWell.class,
                                           new WitsmlQuery(),
                                           "ID-well");

  

3. Refresh an already instantiated object

     
     // Get all attributes from an already loaded wellbore instance
     witsmlServer.refresh(wellbore, new WitsmlQuery());
  


Each JWitsml class representing WITSML instances has getter methods for all the individual properties defined by WITSML for that type.

Attribute names in the JWitsml library does not necessarily correspond exactly to the WITSML definition as the former is specified in a human readable form. Names like dTimKickoff is translated into kickoffTime etc.

     WitsmlMudLog mudLog = ...

     System.out.println("Name: " + mudLog.getName());
     System.out.println("Time: " + mudLog.getTime());
     System.out.println("Mud log company: " + mudLog.getMudLogCompany());
     :
     :
  
Floating point values that may have a unit associated are represented by the Value type which is a compound of a double and a unit symbol string.

The WitsmlQuery class

The WitsmlQuery class is used to control which properties to extract for each object as well as for object filtering.

A default WitsmlQuery instance as shown in the above examples simply means get all properties.

Getting only a subset of the properties of a type is done by specifically including the requested WITSML elements in the WitsmlQuery object:

     
     // Construct a query consisting of the name property only
     WitsmlQuery query = new WitsmlQuery();
     query.includeElement("name");
     
     // Get all wells (name only) from a given server
     List<WitsmlWell> wells = WitsmlServer.get(WitsmlWell.class,
                                               query);
  
Note that when specifying elements and attributes for the WitsmlQuery class, the WITSML terms as specified in the WITSML 1.3 or WITSML 1.4 schema respectively must be used.

Sometimes it is more convenient to get all elements but a few, perhaps elements representing bulk data. In the example below all trajectories of a wellbore are retrieved including all their properties except the trajectory stations:

     
     // Get all trajectories for a wellbore, but exclude the stations
     WitsmlQuery query = new WitsmlQuery();
     query.excludeElement("stations");

     List<WitsmlTrajectory> trajectories = WitsmlServer.get(WitsmlTrajectory.class,
                                                            query,
                                                            wellbore);
  
When an object is initially instantiated with only a few properties, more properties can be loaded using the refresh() method:
     
     // Get wellbore with name property only
     WitsmlQuery query = new WitsmlQuery();
     query.includeElement("name");
     WitsmlWellbore wellbore = WitsmlServer.getOne(WitsmlWellbore.class,
                                                   query,
                                                   "ID-wellbore");
     
     // Get more properties 
     query = new WitsmlQuery();
     query.includeElement("statusWellbore");
     query.includeElement("mdCurrent");
     query.includeElement("tvdCurrent");
     query.includeElement("mdPlanned");
     query.includeElement("tvdPlanned");

     witsmlServer.refresh(wellbore, query);
  
Note that calls to the WitsmlServer.get(), getOne() and refresh() methods might be time consuming, and in most cases it is more efficient to retrieve all attributes in a single server access.

Filtering

The WitsmlQuery class can be used for object filtering as shown in the following example:
     
     // Get all active wellbores of a well
     WitsmlQuery query = new WitsmlQuery();
     query.addElementConstraint("statusWellbore", WitsmlWell.Status.ACTIVE);

     List<WitsmlWellbore> wellbores = witsmlServer.get(WitsmlWellbore.class,
                                                       query,
                                                       well);
  
More than one filter may be specified in the same query, and the same filter may be set repeatedly in order to filter on more than one value:
     
     // Get log including a subset of the available curves and no bulk data
     WitsmlQuery query = new WitsmlQuery();
     query.addElementConstraint("mnemonic", "Depth");
     query.addElementConstraint("mnemonic", "GR");
     query.addElementConstraint("mnemonic", "Dens");
     query.excludeElement("logData");

     WitsmlLog log = witsmlServer.getOne(WitsmlWellbore.class,
                                         query,
                                         "ID-log",
                                         wellbore);
  
Another example:
     
     // Get list of wellbores with known IDs
     WitsmlQuery query = new WitsmlQuery();
     query.addAttributeConstraint("wellbore", "uid", "id1");
     query.addAttributeConstraint("wellbore", "uid", "id2");
     query.addAttributeConstraint("wellbore", "uid", "id3");

     List<WitsmlWellbore> wellbores = witsmlServer.get(WitsmlWellbore.class,
                                                       query,
                                                       well);
  
The last example will not work if it is combined with other element or attribute constraints as JWitsml wouldn't know to which part of the query the additional constraints should be applied. In this case it is possible to use sub-queries to achieve finer control of the query. This example returns the same wellbores as in the example above, but in addition it set constraints for each of them:
     
     // Get list of wellbores with known IDs
     WitsmlQuery query = new WitsmlQuery();

     WitsmlQuery q1 = query.addSubQuery("wellbore");
     q1.addAttributeConstraint("wellbore", "uid", "id1");
     q1.includeElement("name");

     WitsmlQuery q2 = query.addSubQuery("wellbore");
     q2.addAttributeConstraint("wellbore", "uid", "id2");
     q2.includeElement("name");
     q2.includeElement("shape");

     WitsmlQuery q3 = query.addSubQuery("wellbore");
     q3.addAttributeConstraint("wellbore", "uid", "id3");
     q3.addAttributeConstraint("mdCurrent", "uom", "ft");

     List<WitsmlWellbore> wellbores = witsmlServer.get(WitsmlWellbore.class,
                                                       query,
                                                       well);
  

Adding WITSML objects

Writing new data to the WITSML server requires the instantiation of new objects in the client program, specifying the necessary properties and submitting the instance to the server.

New root level objects (Witsml<type>) are created using the WitsmlServer instance as a factory class through the WitsmlServer.newInstance() method. Sub types are always created by using the parent object as a factory class, either through the new<What> or add<What> methods depending on if it has 1:1 or 1:n relationship respectively.

This example creates a WITSML rig instance with some of its defined sub-components:

    
    // Create empty rig instance using factory method
    WitsmlRig rig = witsmlServer.newInstance(WitsmlRig.class,
                                             name,
                                             wellbore);
    
    // Set rig properties
    rig.setType(WitsmlRig.Type.FLOATER);
    rig.setActive(true);
    rig.setRigOwner("Subsea 7");
    rig.setDerrickType(WitsmlRig.DerrickType.SLANT);
    :
    
    // Create rig BOP instance
    WitsmlRig.Bop bop = rig.newBop();
    bop.setModel("Single Ram");
    bop.setManufacturer("Cameron");
    bop.setConnectionSize(new Value(18.625, "in"));
    bop.setPressureRating(new Value(5000.0, "psi"));
    bop.setSize(new Value(new Value(12.0, "in"));
    :
    
    // Add a rig BOP component
    WitsmlRig.Bop.Component component1 = bop.addComponent();
    component1.setType(WitsmlRig.Bop.Component.Type.PIPE_RAM);
    component1.setDescription("WP w/ stainless");
    component1.setInnerPassThruDiameter(new Value(4.0, "in"));
    component1.setWorkingPressure(new Value(20000, "psi"));
    component1.setMinDiameter(new Value(4.0, "in"));
    component1.setMaxDiameter(new Value(12.0, "in"));
    component1.setNomenclature("S");
    component1.setVarable(true);
    :
    
    // Add a second rig BOP component
    WitsmlRig.Bop.Component component2 = bop.addComponent();
    component2.setType(WitsmlRig.Bop.Component.Type.ANNULAR_PREVENTER);
    component2.setDescription("Second WP w/ stainless");
    component2.setInnerPassThruDiameter(new Value(4.0, "in"));
    component2.setWorkingPressure(new Value(22000, "psi"));
    component2.setMinDiameter(new Value(4.0, "in"));
    component2.setMaxDiameter(new Value(12.0, "in"));
    component2.setNomenclature("S");
    component2.setVarable(true);
    :
    
    // Add rig pump instance
    WitsmlRig.Pump pump1 = rig.addPump();
    pump1.setPumpNumber(1);
    pump.setManufacturer("Oilwell");
    pump.setType(WitsmlRig.Pump.Type.TRIPLEX);
    :
    
    //
    // Finally write the compound object to the server
    // 
    try {
      witsmlServer.add(rig);
    }
    catch (WitsmlServerException exception) {
      exception.printStackTrace();
    }
  

Note that calling WitsmlServer.newInstance() only establish Java objects within the client code, it does not cause any remote access.

Unique IDs for new instances are created automatically by JWitsml. If the client needs to control this process, it may install an IdGenerator in the WitsmlServer:

    class MyIdGenerator implements IdGenerator
    {
      public String generateId(String witsmlType, String name, WitsmlObject parent, String defaultId)
      {
        String id = ...;
        return id;
      }
    }

    witsmlServer.setIdGenerator(new MyIdGenerator());
  

Note that this is optional and that JWitsml by default will generate high quality Leach-Salz IDs that are guaranteed to be universally unique.

Updating WITSML objects

When the client program has a reference to an object, new properties can be updated in the server at any time. Use the object setter methods to specify changes and use the WitsmlServer.update() method to propagate the changes back to the server.

This example adds an additional section to an existing WitsmlSurveyProgram instance:

    
    // Create a new section for existing survey program
    WitsmlSurveyProgram.Section section = surveyProgram.addSection();
    section.setSequenceNo(8);
    section.setName("section8");
    section.setMdStart(new Value(250.0, "m"));
    section.setMdEnd(new Value(260.0, "m"));
    :

    try {
      witsmlServer.update(surveyProgram);
    }
    catch (WitsmlServerException exception) {
      exception.printStackTrace();
    }
  

Deleting WITSML objects

Deleting a WITSML objects is done by the WitsmlServer.delete() call:
    
    // Delete a log from the server
    try {
      witsmlServer.delete(log);
    }
    catch (WitsmlServerException exception) {
      exception.printStackTrace();
    }
  

Note that servers may require children to be deleted first, if an object with children is to be deleted. Normally it should be sufficient to delete the root node however.

Unit of measure

WITSML defines an elaborate set of units and unit conversions. More than 2.300 units of more than 250 different petroleum related quantities are defined. JWitsml supports this information through a powerful yet simple API.

WITSML units are accessed through the WitsmlUnitManager singleton class. The unit manager provides an optional service for client programs that are doing computational operations on numerical WITSML data.

Units (such as "meter" or "bar") are organized by WITSML in quantities (such as "length" or "pressure"). Each quantity defines a base unit (typically the SI unit) and potentially a set of derived units.

The following piece of code lists all quantities and associated units available:

     import no.petroware.jwitsml.units.WitsmlUnitManager;
     import no.petroware.jwitsml.units.WitsmlUnit;

     :

     // Get the unit manager
     WitsmlUnitManager unitManager = WitsmlUnitManager.getInstance();

     // Get all known quantities
     List<String> quantities = unitManager.getQuantities();
     for (String quantity : quantities) {
       System.out.println("Quantity: " + quantity);

       // List all units of the quantity
       List<WitsmlUnit> units = unitManager.getUnits(quantity);
       for (WitsmlUnit unit : units)
         System.out.println("  " + unit);
     }
  
Converting between units is done using the convert() method of the WitsmlUnitManager:
     // Get units for feet and centimeters respectively
     WitsmlUnit ftUnit = unitManager.findUnit("ft");
     WitsmlUnit cmUnit = unitManager.findUnit("cm");

     // Convert between the two
     double ftValue = 1.0;
     double cmValue = unitManager.convert(ftUnit, cmUnit, ftValue);
  
When calling a WITSML server a client may specify the preferred return unit for a certain property. In fact JWitsml by default always requests values in their base units. As such requests are defined by the WITSML standard to be regarded as hints only, the client can unfortunately never rely on this feature. Until this is properly sorted out by the standard, the client is always forced to handle unit conversions locally:
     Value currentMd = wellbore.getCurrentMd();

     WitsmlUnit unit = unitManager.findUnit(currentMd.getUnit());
     WitsmlUnit baseUnit = unitManager.findBaseUnit(unit);

     double currentMdInMeters = unitManager.convert(unit, baseUnit,
                                                    currentMd.getValue());
  
As this is such a common operation it can be done using the toBaseUnit() convenience method:
     // Get current wellbore MD in meters
     Value currentMd = unitManager.toBaseUnit(wellbore.getCurrentMd());
  
Note that using the JWitsml unit module never causes any remote WITSML server calls. The unit data base of WITSML is part of the JWitsml delivery and thus local to the client software, and it may be used as a unit conversion engine independently of WITSML.

JWitsml uses version 2.2 of the Energistics unit database which is compatible with WITSML 1.4 and PRODML 1.1. JWitsml has made a number of modifications to this database in order to tune incompatibilities and defects.

Connection logging

A client application may supervise the details of the ETP communication by adding a EtpAccessListener to the EtpServer class:
     public class AccessListener implements EtpAccessListener {

       @Override
       public void access(EtpAccess access) {
         // Logging etc. goes here
       }
     }
  
The access() method is called immedeately before an ETP message is sent from the client and immediately after an ETP message from the server has arrived at the client. The EtpAccess instance contains details about the message, its time stamp, and error codes and so on.

The methods may be used for simple client logging or for creating advanced performance statistics etc.

The equivalent API for WITSML 1 is through WitsmlAccessListener in the WitsmlServer class:

     public class ClientListener implements WitsmlAccessListener {

       @Override
       public void requestSent(WitsmlRequest request) {
         // Logging etc. goes here
       }

       @Override
       public void responseReceived(WitsmlResponse response) {
         // Logging etc. goes here
       }
     }
  
The requestSent() method is called immediately before JWitsml access the remote WITSML server. The responseReceived() method is called immediately after the response is received from the server.

The WitsmlRequest instance contains information such as the time of request, the low level WSDL procedure name, the XML query, the WITSML type being queried etc. The WitsmlResponse instance contains a link to the corresponding request instance as well as the complete XML response, response time, status code, server message and exception if the access failed for some reason.