Skip to main content
Kinetic Community

Kinetic Bridgehub Adapter Creation Training

Topics 

  • Introduction - Bridgehub Tour
    • Adding Adapters
    • Testing Adapters
    • Showing all installed adapters
  • Components of a Bridgehub Adapter
    • Methods
    • BridgeRequest
    • QualificationParser
    • Discovery File
    • JUnit Test File (Optional)
  • Bridgehub Adapter Documentation
    • kinetic-bridgehub-adapter.jar JavaDoc
  • How to Build Bridgehub Adapters (including an introduction to Maven)
    • Java Version 6
    • Maven
      • pom.xml
      • mvn test
      • mvn clean install
  • Java Programming Language
    • Data Types (Map, List, String) and Casting
      • Iterating over Objects
      • Ineritance and @Override methods
      • Working with JSON

Activities 

  • Activity 1

    • Modifying an adapter's configuration properties
    • Implement the count method
    • Test a bridge with a JUnit test
  • Activity 2

    • Implement the search method
    • Test a bridge using the Bridgehub console
  • Activity 3

    • Implement the retrieve method
    • Add an escape query method to handle reserved URL characters in a query
    • Test a bridge using the Test Bridge Adapter Base file
  • Activity 4

    • Implement accepting parameters for each method
    • Add the ability to retrieve a record by using a query (previously only allowed retrieval by submission id)

The training files can be downloaded here.

Detailed Topic Overview

Introduction - Bridgehub Tour

Adding Adapters

When creating a new bridge instance for an adapter in Bridgehub, you will be presented with a list of properties that need to be configured before the bridge can be successfully created. Name, Slug, and Servers Allowed API Address are default and required properties for every bridge and therefore cannot be controlled or changed by an Adapter author. Any properties that follow after that (ie. Username, Password, Kinetic Space Url, Kapp Slug, and Form Slug in the screenshot) are Adapter specific and can be added/modified by the Adapter author.

Testing Adapters

Each bridge instance that has been previously created has its own API Test page, which can be used to quickly test and debug problems in a bridge. On the test page you just need to select a method (count,retrieve,search), a structure, some fields, and a query and the console will generate the API request and return the response.

Showing All Installed Adatpers

If you ever need/want to see a list of all the currently installed adapters, navigate to the Environment tab where there is a list of currently installed adapters at the bottom of the page. Along with the name of the adapters, that list contains information about each adater such as the Version, Build Date, along with the adapter class and .jar location.

Components of a Bridgehub Adapter

Methods

- getName()

Returns the readable name of the current bridge adapter

- getVersion()

Returns the version of the current bridge adapter

- getProperties()

Return the configurable properties and their current values for the current bridge adapter

- setProperties(parameters)

Set the configurable properties to the values supplied in the passed parameter map

- initialize()

Initializes the current bridge adapter. This method is called after the bridge has been created for the first time and then any time the properties are updated after that.

- count(bridgeRequest)

Returns a Count object that contains the number of records the inputted Bridge Request matched.

- retrieve(bridgeRequest)

Returns a Record object containing a key-value map for the specified fields in the matched inputted Bridge Request.

- search(bridgeRequest)

Returns a RecordList object containing a List of Records that matched the inputted Bridge Request.

BridgeRequest

Each of the implementation methods (count,retrieve,search) needs a Bridge Request object to know what to search for in the system records. A Bridge Request object is created from the REST API call that Bridgehub receives and it consists of the following:

Structure

The type of data that is being searched for (ie. Submissions, Users, Incidents, etc.). Similar to a Table in a database.

Fields

A list of fields that will be returned for each record that is found and returned from the data source.

Query

A query that will be used to search the records that match the given structure in the data source. Any records that match the query will be returned from the bridge.

Metadata

A map of the metadata that was passed to the bridge to refine the search of the data source. The most common key-value metadata pairs passed are Order and Pagination (pageNumber,pageSize,offset OR pageToken), but any key-value pair can be passed as metadata and used in the bridge. This is the best place to pass through any data that you need to search the data source but doesn't fit under the Structure, Fields, or Query categories.

Parameters

A map of parameter names and values that will be used to substitute out parameter placeholders for actual values in the query before a call to the data source is made.

Qualification Parser

The qualification parser is a class created to help parse and encode parameters for bridge qualifications. The only method that is required to be implemented in the Qualification Parser is the abstract method encodeParameter. The method is passed a name and value for a parameter and the method needs to know how to encode it so that the parameter value conforms to the standards of the data source before it is attempted to be sent as a part of the query. The most common place where this is used is for data sources that use XML and the encodeParameter method is used to escape reserved XML characters so the adapter doesn't send malformed XML to the data source.

Discovery File

Located at src/main/resource/META-INF/adapters needs to be a file named com.kineticdata.bridgehub.adapter.BridgeAdapter that contains the class for your main adapter file. Bridgehub searches through all .jars in WEB-INF/lib for any that have the discovery file and then attempts to load up the Bridge Adapter if the discovery file was found.

Example: The Kinetic Core adapter's discovery file contains the following:
com.kineticdata.bridgehub.adapter.kineticcore.KineticCoreAdapter

JUnit Test File (Optional)

A JUnit test file can be included to allow testing an adapters functionality without loading it into Bridgehub. This isn't required, but it allows you to test situations that you have a static set of inputs and expected outputs much more quickly than manually tesing in Bridgehub (ie. Testing that bad inputs lead to the correct errors).

Bridgehub Adapter Documentation

kinetic-bridgehub-adapter.jar JavaDoc

If you want more specific information about methods available to you when creating a bridge adapter, download the kinetic-bridgehub-adapter.jar JavaDoc and explore for yourself. To view the JavaDoc after downloading, unzip the folder and open up index.html in your browser of choice.

Download: kinetic-bridgehub-adapter-1.0.3-javadocs.zip

How to Build Bridgehub Adapters (including an introduction to Maven)

Java Version 6

The Bridgehub console and Bridgehub adapters are currently targeting Java 6.

Maven

pom.xml

Version

 This is where the Maven build version for your project is located (which is different than the display version which is set in the adapters getVersion() method). The Maven version number is displayed in the name of the .jar file after it has been built as well being used to reference the project from a different project. It is not required to do so, but it generally is recommended to keep the Display version number and the Maven version number in sync.

Dependencies

If you need to include outside libraries in your project (ie. HttpClient, Apache Commons, external .jars, etc) this is done under the dependencies section of the pom.xml. This is most easily managed in an IDE such as Netbeans or Eclipse, which will handle adding the libraries (and any of their dependencies) to the pom.xml. On project build, Maven will automatically download any libraries that aren't already on your machine so that you don't have to worry about finding and downloading them yourself. If you want more specifics about how Maven dependencies work, you can read about it on their website: https://maven.apache.org/guides/intr...ncy_Management.

mvn test

mvn test is the command line command to run any tests associated with your project. The output for the command will tell you which of your tests passed/failed. It also shows you where to go to get more specific information on why your tests failed (if any of them did)

mvn clean install

mvn clean install is the command line command to clean the project (remove the target directory) and then build the .jar for the project. After the command is successful, the file that was build will be found in the target directory.

Java Programming Language

Data Types (Map, List, String) and Casting

- String
String string = "This is a string";
  A standard string.
- Map

Map map = new HashMap();
  This is a standard map; Order is not guaranteed.

Map map = new LinkedHashMap();
  A LinkedHashMap is the same as a HashMap, but order is guaranteed to be the same as the order that items are placed into the map.

  How to add an item to a map
map.put(key,value);

- List

List list = new ArrayList();
  A standard list.

  How to add an item to a list
list.add(value);

- Collection Types

When creating a collection (List,Map,etc), a type can be specified at creation. For example, if you want to have a list that can only contain string, you would create the list like this:

List listOfStrings = new ArrayList<String>();

The <String> tells Java what type of object is allowed to be passed into the collection. For Maps, you can pass an object type for both the key and value like this:

Map mapOfStringObject = new HashMap<String,Object>();

Similar to the List, this tells us that all keys need to be strings, but the value is allowed to be any Java object.

- Casting

Casting allows you to take a previously created variable and change its type to another variable. This is most frequently done when taking a variable from a more general type and moving to a more specific type. For example, if you have an item stored in a general Object varaible but know that the object that the variable is holding is a string, you could cast the variable as a String. The more specific type (String in this case) would then give you access to specific String methods that you can perform on the variable as opposed to be constrained to just the general Object variables.

Iterating Over Objects

Iterating Over a Specific Length

for (int i=0; i<100;i++) {
    System.out.println(i);
}

Iterating Over a List

List<String> list = new ArrayList<String>();
for (String item : list) {
    System.out.println(item);
}

Iterating Over a Map

Map<String,String> map = new HashMap<String,String>();
for (Map.Entry<String,String> entry : map.entrySet()) {
    System.out.println(entry.getKey());
    System.out.println(entry.getValue());
}

Working with JSON

Using the JSON Simple Library

String jsonString = "{\"key\" : \"value\"}";
// How to parse from a String to a JSON Object
JSONObject json = (JSONObject)JSONValue.parse(jsonString);
// How to get an object from the JSON Object
Object object = json.get("object");
// How to get a string from the JSON Object
String string = json.get("string").toString();
// How to get a second level JSON Object
JSONObject secondJson = (JSONObject)json.get("moreJson");
// How to retrieve a JSON array
JSONArray array = (JSONArray)json.get("array");

Inheritance and @Override methods

@Override is an annotation that is used to denote that the current class is overriding a method that existed in the class that is currently being extended or inherited (if you want to know more about inheritance, you can read about it here). All Bridgehub adapters inherit from the abstract class BridgeAdapter, so properly implement the class you need to @Override all the abstract methods that are defined in BridgeAdapter (as detailed earlier).

Detailed Activity Guide

Activity 1

How to add a new configuration property

Step 1: Add the property variable and display name to the static Properties class

public static final String PROPERTY_KAPP_SLUG = "Kapp Slug";

Step 2: Add the configurable property to the Configurable Property Map using the previously created variable name

new ConfigurableProperty(Properties.PROPERTY_KAPP_SLUG)

Step 3: Add any needed conditions to the previously created Configurable Property object (required, sensitive, dependency, etc)

new ConfigurableProperty(Properties.PROPERTY_KAPP_SLUG).setIsRequired(true)

Implementing the count method

Step 1: Get the size of the JSONArray that was returned from the data source

Integer count = submissions.size();

Step 2: Create a new Count object using the retrieved size and return it

return new Count(count);

Testing the bridge using a JUnit test

Step 1: Input your username,password, and kinetic space url into the config values in the TrainingSubmissionsTest.java file (the file is located at src/test/java/TrainingSubmissionsTest.java).

configValues.put("Username","myusername");
configValues.put("Password", "mypassword");
configValues.put("Kinetic Space Url","http://myIpAddress:8080/kinetic/ce-training");
configValues.put("Kapp Slug","admin");
configValues.put("Form Slug","movies-for-rent");

Step 2: Run mvn test to see if the test successfully passes. If you see BUILD SUCCESS you have successfully implemented the count method!

Activity 2

Implementing the Search method

Step 1: Copy the code for retrieving the source data from the count method. All the code under the following comment headers should be the same between the methods:

// Build up the url that you will use to retrieve source data
// Initialize the HTTP Client,Response, and HTTP GET objects
// Append HTTP BASIC Authorization header to HttpGet call
// Make the call to the source to retrieve data and convert the response from a HttpEntity object into a Java String

Step 2: Iterate through the submission list and make a new Record for each submission JSONObject

for (Object o : submissions) {
    JSONObject submission = (JSONObject)o;

    // Create a Record object from the returned submission JSONObject
    Record record;
    if (submission != null) {
        // If the submission exists, create a Record by passing in the
        // submission JSONObject
        record = new Record(submission);
    } else {
        // If the submission doesn't exist, create an empty Record
        record = new Record();
    }
    // Add the created record to the list of records
    recordList.add(record);
}

Step 3: Return a new RecordList object (The order of the parameters that need to be passed to RecordList are fields, records, then metadata)

return new RecordList(request.getFields(), recordList, metadata);

Test the bridge using the Bridgehub console

Step 1: Change directories to kinetic-bridgehub-adapter-training-users

cd kinetic-bridgehub-adapter-training-users

Step 2: Build the project

mvn clean install

Step 3: Copy the created .jar from /target to kinetic-bridgehub/WEB-INF/lib

cp target/kinetic-bridgehub-adapter-training-users-1.0.0.jar tomcat/webapps/kinetic-bridgehub/WEB-INF/lib

Step 4: Restart tomcat

sudo service tomcat restart

Step 5: Create a new instance of the bridge in Bridgehub

Step 6: Navigate to the Test page for the bridge

Step 7: Test the search method with the following inputs:

Structure: Submissions

Fields: id,values

Query: (empty)

Activity 3

Implement the Retrieve method

Step 1: Using the Kinetic Core REST documentation, build up a url to retrieve a submission that matches a single submission id

String url = this.spaceUrl+"/app/api/v1/submissions/"+submissionId+"?include=values,details";

Step 2: Similar to the Search method, copy the code for retrieving the source data from the count or search method.

Step 3: Create a record object from the returned submission object.

Record record;
if (submission != null) {
    // If the submission exists, create a Record by passing in the
    // submission JSONObject
    record = new Record(submission);
} else {
    // If the submission doesn't exist, create an empty Record
    record = new Record();
}

Step 4: Return the record.

return record;

Add an escape query method to handler reserved URL characters in a query

Step 1: Review the escapeQuery() method provided at the bottom of the file

Step 2: Find any places where a query is being added to a url. Then run the query through the escapeQuery() method before adding it to the url.

Example: String url = this.submissionsEndpoint+"?limit=1000&"+escapeQuery(query);

Test a bridge using the Test Bridge Adapter Base file

Step 1: Add extends BridgeAdapterTestBase to the JUnit test class.

Step 2: Add the getAdapterClass() method.

Step 3: Add the getConfigFilePath() method.

Step 4: Find the bridge-config.yml file and add your username,password, and Kinetic Space Url to the Bridge Configuration section

Step 5: Change directories to kinetic-bridgehub-adapter-training-users

Step 6: Run mvn test. Even if you successfully implemented the retrieve method, 2 of the retrieve methods are still going to be failing. So you will get a BUILD FAILURE method either way (don't worry, this will be fixed in the next activity!).

Activity 4

Implement using parameters for each method

Step 1: Parse the query and exchange out any parameters with their parameter values (add the following code chunk under the corresponding comment in the count, retrieve, and search methods).

TrainingSubmissionsQualificationParser parser = new TrainingSubmissionsQualificationParser();
String query = parser.parse(request.getQuery(),request.getParameters());

Step 2: Replace any places where request.getQuery() is used with the generated query variable.

Add the ability to retrieve a record by using a query (previously only allowed retrieval by submission id)

Step 1: Move the code used to retrieve a JSONArray of submissions into a helper method.

Step 2: If there is no submission id provided in the query, send the query into the helper method to return matching submissions.

Step 3: If there is more than one submission that matches the query, throw an error. If there is a single record, use that to populate the submission object and create a Record.

JSONArray submissions = getSubmissions(query);
if (submissions.size() == 1) {
    submission = (JSONObject)submissions.get(0);
} else if (submissions.size() > 1) {
    throw new BridgeError("Multiple results matched an expected single match query");
}

Step 4: Re-test the adapter against the TestBridgeAdapterBase. Adding in the ability to retrieve a record using a query should fix the errors that were happening with the retrieve tests!