Skip navigation
Share:|

I really like the principle of BMC Integration Service. It solves the problem of connecting your Cloud application to your on-premise application without having to set up a whole array of gateways, proxies and what not. I also appreciate the principle of linking two connectors via a flow without using code; it’s well, rather elegant. But then again, I do enjoy a bit of coding.

 

So, let’s open the bonnet of Integration Service and try to write our own connector. In the past I would have written an Innovation Suite based Java Service but I want to take advantage of the Integration Service platform. I want my code to run on the platform, I want to use the triggers, use them in flows with other connectors. But how do you get started? Well, let me explain.

 

Integration Service uses the principle of Sites. That’s how you get the connectors to talk to your on-premise application: a site provides a link between Integration Service connectors and the local network. If you want to write your own connector you need to set up a Development Site. This allows you to install an application called Connector Designer which, you never guess, helps you to design the connector. The installation is fairly straightforward and I assume you’ll manage to get it installed without too many hitches.

 

Once it’s up-and-running, log in via https://localhost:3000 (ignore the certificate error) and click on Integration Controller. The Designer needs to know what Development Site on what Integration Service you are talking to – the two need to be aware of each other. If all is okay a green check-mark will appear and you’re all set.

 

is1.png

 

Click on Connectors -> New Connector to get started. The first thing it will ask you is what language you’re using. Connectors run on the server, so when it says JavaScript it actually means NodeJS. Since I’m more of a Java developer, I decided to go for Java. For the sake of the exercise I suggest you join me.

 

is2.png

 

Before we continue, let’s talk briefly about what we’re trying to accomplish. I want to write a connector which populates a Record Definition of my Innovation Suite app. Innovation Suite comes with a REST-based API so it’s well suited for this. The interface we have in front of us allows us to set up the basics for this: name, description, triggers, action, etc. But when it comes to defining the REST calls, it makes the assumption that one action equals one REST call. That’s not always the case of course, and certainly not for my connector.

Innovation Suite makes it possible to define custom REST resources. These expose a HTTP interface and I created one for my application. To create an entry, this is what I’d need to do on HTTP level:

 

POST https://developer0000.innovate.bmc.com/api/rx/application/command HTTP/1.1
Host: developer0000.innovate.bmc.com
Accept: application/json
X-Requested-By: XMLHttpRequest
Content-Type: application/json;charset=UTF-8
Content-Length: 150

{
"resourceType":
"com.bmc.arsys.rx.application.user.command.LoginCommand",
"username": "user",
"password": "password",
"locale": "en-us"
}

POST https://developer0000.innovate.bmc.com/api/rx/application/record/recordinstance HTTP/1.1
Host: developer0000.innovate.bmc.com
Accept: application/json
X-Requested-By: XMLHttpRequest
Content-Type: application/json;charset=UTF-8
Cookie: AR-JWT=eyJhbGciOiJ...
Content-Length: 493

{
  "resourceType": "com.bmc.arsys.rx.services.record.domain.RecordInstance",
  "id": null,
  "displayId": null,
  "recordDefinitionName": "com.example.application:RecordDef1",
  "permittedGroupsBySecurityLabels": null,
  "permittedUsersBySecurityLabels": null,
  "permittedRolesBySecurityLabels": null,
  "fieldInstances": {
    "10518002": {
      "resourceType": "com.bmc.arsys.rx.services.record.domain.FieldInstance",
      "id": 8,
      "value": "TEST"
    }
  }
}

POST https://developer0000.innovate.bmc.com/api/rx/application/command HTTP/1.1
Host: developer0000.innovate.bmc.com
Accept: application/json
X-Requested-By: XMLHttpRequest
Content-Type: application/json;charset=UTF-8
Content-Length: 82
Cookie: AR-JWT=eyJhbGciOiJ...

{
"resourceType":
"com.bmc.arsys.rx.application.user.command.LogoutCommand"
}

 

I am using Innovation Suite's Record Service application API here. If you plan more elaborate interaction with your IS application, it's worth considering writing a dedicated REST resource so you can tailor it to your specific needs. In this example, I have at least three calls and I have additional problems with specific date/time formats. I need to write code to accomplish this.

 

Does this mean that all the options in Connector Designer are irrelevant? No, I need those to set up the basic framework. Let’s go through the various tabs and set up what we need:

 

is3.png

is4.png

 

The purpose of Name, Description etc are all obvious. But Authorization requires a bit of explanation. If you’re in Integration Service you can add Accounts to Connectors. This is the username/password a Connector needs to function. In its most basic format you need at least a username and a password. You define these under Authorization. So the section you see in the Designer relates to this part in Integration Service:

 

is5.png

 

Let’s keep it simple for the moment. Just specify the username and password and set the Type to Basic.

 

So what else does my Connector need that should be configurable? I want users to specify the base URL of the REST API. That’s done via fields under Configuration. What you set here relates to this part in Integration Service:

 

is6.png

 

I’m going to leave the Triggers for now because I want to keep it as simple as possible. But you get the idea: this is where the connector checks for updates, changes, etc. We’ll have a look at these in a later blog article.

 

What I definitely need to define is the Action. I specify the name and the Input Fields, I don’t need the rest since I’m planning to code the actual communication. Here’s what this looks like:

 

is7.png

 

And that’s all I need at the moment, the configuration is complete: I defined the name, description, I set up the authentication details, configuration items and my action. Go to Build and clock on Generate. What’s next? Let’s write some code!

 

Remember we chose Java as the programming language? When you clicked on Generate, Connector Designer created a Maven project in the folder c:\Users\<username>\panama\connectors. You can either import the Maven project into Eclipse or else just open the Java file in \src\main\java\com\bmc\panama. This file makes up the whole connector and is based on your settings in the Designer. If you scroll through this you’ll recognise some of these. But treat it like a normal Java project. In my case I first define a few private variables I’ll need through the project. These are placed under class:

 

public class TestConnectorForISApplicationConnector extends Connector
  private static final Logger log = LogManager.getLogger("TestConnectorForISApplicationConnector");
  private Map<String, Object> configParameters;

  private HttpsURLConnection urlConnection = null;  
  private String strBaseUrl, strUsername, strPassword;

 

There are a few places you need to write your code. The first is in the method Initialize. This is executed when the Connector is first executed. Remember the Authorization and Configuration details we specified earlier? You now need to handle these. I am retrieving my username and password and of course the Base URL which I previously defined as private variables:

 

@Override
public void initialize( Configuration configuration ) throws Exception {
  log.debug("Initializing  Connector");

  configParameters = configuration.getParameters();
  strBaseUrl = (String) configParameters.get("baseURL");
  if (strBaseUrl.endsWith("/")) {
    strBaseUrl = strBaseUrl.substring(0, strBaseUrl.length() - 1);
  }

  strUsername = _profile.getUsername();
  strPassword = _profile.getPassword();
}

      

So at this stage I have the Base URL, username and password stored in variables. Next, look for the method validateConnection. This is called whenever the user tries to authenticate when adding an account to the Connector. Your aim here is to check if the user can truly log in, if it's a valid user. There’s a Boolean called ‘successful’ defined which you need to set to true. In my case I do a login and logout action. If this fails for whatever reason it will just go to the catch section so that’s where I set the Boolean value. Notice I am using HttpsURLConnection to send the requests, and that the code isn’t specific to Integration Service. It’s normal Java code, I’d use the same code if I had built a Java based client that’s not based on Integration Service. I'm just sending normal HTTP requests to handle the login/logout calls. Check the HTTP requests I quoted earlier for reference. Of course, you need to ensure you’ve got the proper imports handled.

 

@Override
public ValidationResult validateConnection( ValidateConnectionRequest request ) {
  log.debug("Processing Validation Request.");
  boolean successful = false;
  ArrayList<FlowError> errors = new ArrayList<FlowError>();
  try {
    //LOGIN
    URL urltorequest = new URL(strBaseUrl + "/api/rx/application/command");    
    urlConnection = (HttpsURLConnection) urltorequest.openConnection();
    urlConnection.setRequestMethod("POST");
    urlConnection.setConnectTimeout(5000);
    urlConnection.setReadTimeout(5000);
    urlConnection.setRequestProperty("Content-Type", "application/json");
    urlConnection.setRequestProperty("X-Requested-By", "XMLHttpRequest");            
    urlConnection.setDoInput(true);
    urlConnection.setDoOutput(true);            
    String strRequest =  "{\"resourceType\":\"com.bmc.arsys.rx.application.user.command.LoginCommand\",\"username\": \"" + strUsername + "\",\"password\": \"" + strPassword + "\",\"locale\": \"en-us\"}";
    byte[] outputInBytes = strRequest.getBytes("UTF-8");
    OutputStream os = urlConnection.getOutputStream();
    os.write(outputInBytes);   
    os.close();
    InputStream in = new BufferedInputStream(urlConnection.getInputStream());
    String authcode = new Scanner(in).useDelimiter("\\A").next();          
            
    //LOGOUT
    urltorequest = new URL(strBaseUrl + "/api/rx/application/command");    
    urlConnection = (HttpsURLConnection) urltorequest.openConnection();
    urlConnection.setRequestMethod("POST");
    urlConnection.setConnectTimeout(5000);
    urlConnection.setReadTimeout(5000);
    urlConnection.setRequestProperty("Accept", "application/json");
    urlConnection.setRequestProperty("Content-Type", "application/json");
    urlConnection.setRequestProperty("X-Requested-By", "XMLHttpRequest");
    urlConnection.setRequestProperty("Cookie", "AR-JWT=" + authcode);            
    urlConnection.setDoInput(true);
    urlConnection.setDoOutput(true);            
    strRequest =  "{\"resourceType\":\"com.bmc.arsys.rx.application.user.command.LogoutCommand\"}";
    outputInBytes = strRequest.getBytes("UTF-8");
    os = urlConnection.getOutputStream();
    os.write(outputInBytes);  
    urlConnection.getResponseCode();
    os.close();

    successful = true;
  } catch (Exception e) {
    errors.add(new FlowError("ConnectionValidationError", e.getMessage(), null, null));
    successful = false;
  }
  ValidationResult result = new ValidationResult(successful, errors);
  return result;
}

 

When the action is executed the Connectors calls the method Create_Record which it already created for me, the name is based on the name I specified when setting up the Connector. Similar to Initialize there’s a Boolean value which determines if the Action has been successful or not and you need to set this. There are a few things you have to do first though: the variable data contains all the input elements you set when you designed the connector. Here’s how you retrieve these through data.get:

 

private ActionResult Create_Record(ExecuteActionRequest request, PanamaRegistration.Action action) {
  boolean successful = false;
  Map<String, Object> data = request.getData();
  HashMap<String, Object> output_data = new HashMap<String, Object>();
  ArrayList<FlowError> errors = new ArrayList<FlowError>();

  String description = (String) data.get("description");
  data.remove("description");

 

I store these in local variables. From then onwards it’s a matter of calling your API. In my case that’s a series of HTTP calls using HttpsURLConnection again:

 

try {
  //LOGIN
  URL urlRequest = new URL(strBaseUrl + "/api/rx/application/command");    
  urlConnection = (HttpsURLConnection) urlRequest.openConnection();
  urlConnection.setRequestMethod("POST");
  urlConnection.setConnectTimeout(5000);
  urlConnection.setReadTimeout(5000);
  urlConnection.setRequestProperty("Content-Type", "application/json");
  urlConnection.setRequestProperty("X-Requested-By", "XMLHttpRequest");            
  urlConnection.setDoInput(true);
  urlConnection.setDoOutput(true);            
  String strRequest =  "{\"resourceType\":\"com.bmc.arsys.rx.application.user.command.LoginCommand\",\"username\": \"" + strUsername + "\",\"password\": \"" + strPassword + "\",\"locale\": \"en-us\"}";
  byte[] outputInBytes = strRequest.getBytes("UTF-8");
  OutputStream os = urlConnection.getOutputStream();
  os.write(outputInBytes);   
  os.close();
  InputStream in = new BufferedInputStream(urlConnection.getInputStream());
  String authcode = new Scanner(in).useDelimiter("\\A").next();          

  //CREATE
  urlRequest = new URL(strBaseUrl + "/api/rx/application/record/recordinstance");    
  urlConnection = (HttpsURLConnection) urlRequest.openConnection();
  urlConnection.setRequestMethod("POST");
  urlConnection.setConnectTimeout(5000);
  urlConnection.setReadTimeout(5000);
  urlConnection.setRequestProperty("Accept", "application/json");
  urlConnection.setRequestProperty("Content-Type", "application/json");
  urlConnection.setRequestProperty("X-Requested-By", "XMLHttpRequest");
  urlConnection.setRequestProperty("Cookie", "AR-JWT=" + authcode);
  urlConnection.setDoInput(true);
  urlConnection.setDoOutput(true);
  strRequest = "{\"resourceType\":\"com.bmc.arsys.rx.services.record.domain.RecordInstance\",\"id\":null,\"displayId\":null,\"recordDefinitionName\":\"com.example.escalada:Incident\",\"permittedGroupsBySecurityLabels\":null,\"permittedUsersBySecurityLabels\":null,\"permittedRolesBySecurityLabels\":null,\"fieldInstances\":{\n" +
  "\"7\":{\"resourceType\":\"com.bmc.arsys.rx.services.record.domain.FieldInstance\",\"id\":7,\"value\":\"New\"},\n" +
  "\"8\":{\"resourceType\":\"com.bmc.arsys.rx.services.record.domain.FieldInstance\",\"id\":8,\"value\":\"" + description + "\"}}}" +
  
  outputInBytes = strRequest.getBytes("UTF-8");
  os = urlConnection.getOutputStream();
  os.write(outputInBytes);
  urlConnection.getResponseCode();
  os.close();

 //LOGOUT
  urlRequest = new URL(strBaseUrl + "/api/rx/application/command");    
  urlConnection = (HttpsURLConnection) urlRequest.openConnection();
  urlConnection.setRequestMethod("POST");
  urlConnection.setConnectTimeout(5000);
  urlConnection.setReadTimeout(5000);
  urlConnection.setRequestProperty("Accept", "application/json");
  urlConnection.setRequestProperty("Content-Type", "application/json");
  urlConnection.setRequestProperty("X-Requested-By", "XMLHttpRequest");
  urlConnection.setRequestProperty("Cookie", "AR-JWT=" + authcode);            
  urlConnection.setDoInput(true);
  urlConnection.setDoOutput(true);            
  strRequest =  "{\"resourceType\":\"com.bmc.arsys.rx.application.user.command.LogoutCommand\"}";
  outputInBytes = strRequest.getBytes("UTF-8");
  os = urlConnection.getOutputStream();
  os.write(outputInBytes);  
  urlConnection.getResponseCode();
  os.close();
  successful = true;
} catch ( Exception e ) {
  errors.add(new FlowError("ServiceError", e.getMessage(), null, null));
  successful = false;
}

 

For the sake of the exercise I'm just typing in the JSON code. There are of course better ways to construct this, what I'm doing is error prone and not very elegant. In a real project I'd use something like org.json to construct the JSON code.  When all is done, I set the Boolean to true. If it fails I set it to false. And that’s all you need; your Connector is done: let’s compile it.

 

To do this, go back to Connector Designer. On the tab Build there are four buttons. We already tried Generate to get the code. Now, click on Register which compiles the code and moves it to the Integration server. If all goes okay, start it using the Start button. It could be that you see a compile error which mean there’s a problem with your code. Unfortunately it can’t be more specific than that, and you manually need check your code for any mistakes you might have made. To avoid this I often start with a simple standalone Java class, just to ensure all the HTTP calls work the way I expect it to and I have all the right imports.

 

If all went well your Connector should now show up in Integration Studio and you’re ready to put it to the test.

 

is8.png

 

Set your configuration, add your account and use it a flow:

 

is9.png

 

And that's it! I hope this was useful and I’m looking forward to seeing all sorts of interesting connectors coming up. I realise I didn’t cover everything, nor was this my intention. I just want to explain enough to get you on your way. Just remember to use Connector Designer to set up the framework: username, configuration parameters, triggers, action, etc. Then treat it like a normal Java (or NodeJS) project. Look up the methods I highlighted and add your code.

 

I usually check the code I use for the communication in a separate project, like a simple Java class. I want to make sure everything works the way I'd expect. It saves a lot of time trying to work out why it isn't working in the connector.

I'll write another article to look at some of the other features: how to write triggers and how to use the logs. If you plan to use this in combination with an Innovation Suite application like I did, I'd also encourage you to use a custom REST resource, rather than the API I use. But we'll cover that in another article as well.

 

Best wishes,

Justin