For a few weeks now, Christian Guedemann and I have been working on a Java API to send requests to Watson Work Services to interact with a Watson Workspace. I blogged about the approach recently. I was determined to avoid Java developers having to write strings of JSON data to pass as queries to Watson Work Services. Over the weekend I saw a tweet from Christian where he had incorporated the code into a Workspace Client 4 Eclipse plugin:

It’s been a learning curve for me, getting me very familiar with test-driven design, using enums that extend an interface and more. But I think the code that we have on OpenNTF’s Stash helps make a more readable query and reduces the risk of errors. To give an example, here’s a basic query to get spaces, some properties and its members:

  1.     public void testGetSpacesBasicAsApp(String appId, String appSecret)
  2.             throws UnsupportedEncodingException, WWException {
  3.         WWClient client = WWClient.buildClientApplicationAccess(appId, appSecret, new WWAuthenticationEndpoint());
  4.         assert !client.isAuthenticated();
  5.         client.authenticate();
  6.         assert client.isAuthenticated();
  7.         WWGraphQLEndpoint ep = new WWGraphQLEndpoint(client);
  8.         ObjectDataSenderBuilder createdBy = new ObjectDataSenderBuilder(SpaceChildren.CREATED_BY.getLabel());
  9.         createdBy.addField(PersonFields.ID);
  10.         createdBy.addField(PersonFields.DISPLAY_NAME);
  11.         createdBy.addField(PersonFields.PHOTO_URL);
  12.         createdBy.addField(PersonFields.EMAIL);
  13.         // Basic updatedBy ObjectDataBringer – same label for all
  14.         ObjectDataSenderBuilder updatedBy = new ObjectDataSenderBuilder(SpaceChildren.UPDATED_BY.getLabel());
  15.         updatedBy.addField(PersonFields.ID));
  16.         updatedBy.addField(PersonFields.DISPLAY_NAME);
  17.         updatedBy.addField(PersonFields.PHOTO_URL);
  18.         updatedBy.addField(PersonFields.EMAIL);
  19.         ObjectDataSenderBuilder spaces = new ObjectDataSenderBuilder(“spaces”, true);
  20.         spaces.addAttribute(BasicPaginationEnum.FIRST, 100);
  21.         spaces.addPageInfo();
  22.         spaces.addField(SpaceFields.ID);
  23.         spaces.addField(SpaceFields.TITLE);
  24.         spaces.addField(SpaceFields.DESCRIPTION);
  25.         spaces.addField(SpaceFields.UPDATED);
  26.         spaces.addChild(updatedBy);
  27.         spaces.addField(SpaceFields.CREATED);
  28.         spaces.addChild(createdBy);
  29.         ObjectDataSenderBuilder members = new ObjectDataSenderBuilder(SpaceChildren.MEMBERS.getLabel(), true);
  30.         members.addAttribute(BasicPaginationEnum.FIRST, 100);
  31.         members.addField(PersonFields.ID);
  32.         members.addField(PersonFields.PHOTO_URL);
  33.         members.addField(PersonFields.EMAIL);
  34.         members.addField(PersonFields.DISPLAY_NAME);
  35.         spaces.addChild(members);
  36.         BaseGraphQLQuery queryObject = new BaseGraphQLQuery(“getSpaces”, spaces);
  37.         ep.setRequest(new GraphQLRequest(queryObject));
  38.         ep.executeRequest();
  39.         List<? extends Space> spacesResult = ep.getResultContainer().getData().getSpaces().getItems();
  40.         System.out.println(“Total spaces found – “ spacesResult.size());
  41.         assert (spacesResult.size() > 0);
  42.     }

This may seem pretty complex on first review, but let’s break it down.

Lines 3-7 authenticate the application making the call and set up an endpoint to make the query call. We then create an ObjectDataSenderBuilder, a Java object that can have attributes (to filter the results based on scalar fields), fields corresponding to scalar values that can be returned, and children corresponding to other ObjectDataSenderBuilder Java objects to return child objects. So for a space, that will be members, or the updatedBy and createdBy objects which correspond to Profiles in WWS.

Initially we create two ObjectDataSenderBuilder objects – one for the createdBy Profile and one for the updatedByProfile, for the creator and last updater of the Space. For each we return four fields – ID, display name, photo URL and email. To avoid typos when adding the fields, attributes and children, enums are available. One set of enums is available for fields and attributes (PersonFields for a Profile) and one set for children (PersonChildren for Profiles). So the two objects are created in lines 9-20.

Line 22 then creates the spaces object. Line 23 then adds an attribute to filter on the first attribute, to get the first 100. There is validation includes in the addAttribute method to ensure the correct datatype is passed across. We know the API expects an integer, so passing a String “100” will throw the following error on line 23:

org.opencode4workspace.WWException: Watson Work Services expects a java.lang.Integer for this attribute. Object supplied is java.lang.String

Line 24 then adds a basic PageInfo ObjectDataSenderBuilder, so subsequent calls know the current page and whether there are any before or after. Lines 25-31 add the fields and children we want to return.

Lines 32-38 then add a members ObjectDataSenderBuilder filtered to the first 100 members, returning some scalar fields. There’s no intention subsequently to page, so a PageInfo ObjectDataSenderBuilder is not added. Finally, and crucially, we add this members object as a child to spaces.

Line 40 creates a BaseGraphQLQuery object, passing the query name and the top-level ObjectDataSenderBuilder. This will automatically convert the query to JSON. Line 41 passes the query object to a new GraphQLRequest, which extracts the operation name (“getQuery”) and could optionally take variables. Line 42 executes the request and line 43 parses the result to get a List of Space objects, which can then be processed using Java.

As I say, this may seem a bit complex, and so the WWGraphQLEndpoint object has a getSpaces() method to return a generic set of fields and children. But the whole point of GraphQL is to be able to control what data you’re returning, hence using the kind of process outlined above. The use of enums and relevant Java methods is intended to avoid errors or catch them before the query is passed to WWS, thus clearly identifying where the code is incorrect.

There is still a lot of work required. Javadoc documentation has been added from the start, as have tests which will help implementation. Documentation will also be added to OpenNTF’s Confluence wiki. Any feedback is also welcomed.

Leave a Comment

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.