Overview

This Java package encapsulates the code required by an LTI 1 compliant tool provider to communicate with a tool consumer. It includes support for LTI 1.1 and the unofficial extensions to Basic LTI. A similar set of PHP classes is also available.

Whilst supporting LTI 1 is relatively simple, the benefits to using a class library like this one are:

System requirements

The LTI Tool Provider package is written for Java 6 (or higher) and references the following dependent libraries:

A copy of these dependent library files can also be found in the Blackboard Building Block version of the sample Rating tool provider web application which is available as a separate download for this project.

Installation

Java package

The download file available from OSCELOT (see Licence section below) includes the LtiToolProvider.jar file which contains all the class files. Add this file to your Java web project.

Data connector classes

These classes persist data through an extension of the DataConnector class. The jar file includes an implementation for JDBC connections (org.oscelot.lti.dataconnector.JDBC) which uses a standard data structure. The data may alternatively be merged with an existing tool provider data schema by creating an alternative DataConnector class which retrieves and updates each object from the appropriate location. To use the classes without persisting any data use the org.oscelot.lti.dataconnector.None class.

The standard JDBC connector uses the following database tables and relationships:

CREATE TABLE lti_consumer (
  consumer_key varchar(255) NOT NULL,
  name varchar(45) NOT NULL,
  secret varchar(32) NOT NULL,
  lti_version varchar(12) DEFAULT NULL,
  consumer_name varchar(255) DEFAULT NULL,
  consumer_version varchar(255) DEFAULT NULL,
  consumer_guid varchar(255) DEFAULT NULL,
  css_path varchar(255) DEFAULT NULL,
  protected tinyint(1) NOT NULL,
  enabled tinyint(1) NOT NULL,
  enable_from datetime DEFAULT NULL,
  enable_until datetime DEFAULT NULL,
  last_access date DEFAULT NULL,
  created datetime NOT NULL,
  updated datetime NOT NULL,
  PRIMARY KEY (consumer_key)
) ENGINE=InnoDB DEFAULT CHARSET=latin1;

CREATE TABLE lti_context (
  consumer_key varchar(255) NOT NULL,
  context_id varchar(255) NOT NULL,
  lti_context_id varchar(255) DEFAULT NULL,
  lti_resource_id varchar(255) DEFAULT NULL,
  title varchar(255) NOT NULL,
  settings text,
  primary_consumer_key varchar(255) DEFAULT NULL,
  primary_context_id varchar(255) DEFAULT NULL,
  share_approved tinyint(1) DEFAULT NULL,
  created datetime NOT NULL,
  updated datetime NOT NULL,
  PRIMARY KEY (consumer_key, context_id)
) ENGINE=InnoDB DEFAULT CHARSET=latin1;

CREATE TABLE lti_user (
  consumer_key varchar(255) NOT NULL,
  context_id varchar(255) NOT NULL,
  user_id varchar(255) NOT NULL,
  lti_result_sourcedid varchar(255) NOT NULL,
  created datetime NOT NULL,
  updated datetime NOT NULL,
  PRIMARY KEY (consumer_key, context_id, user_id)
) ENGINE=InnoDB DEFAULT CHARSET=latin1;

CREATE TABLE lti_nonce (
  consumer_key varchar(255) NOT NULL,
  value varchar(32) NOT NULL,
  expires datetime NOT NULL,
  PRIMARY KEY (consumer_key, value)
) ENGINE=InnoDB DEFAULT CHARSET=latin1;

CREATE TABLE lti_share_key (
  share_key_id varchar(32) NOT NULL,
  primary_consumer_key varchar(255) NOT NULL,
  primary_context_id varchar(255) NOT NULL,
  auto_approve tinyint(1) NOT NULL,
  expires datetime NOT NULL,
  PRIMARY KEY (share_key_id)
) ENGINE=InnoDB DEFAULT CHARSET=latin1;

ALTER TABLE lti_context
  ADD CONSTRAINT lti_context_consumer_FK1 FOREIGN KEY (consumer_key)
   REFERENCES lti_consumer (consumer_key);

ALTER TABLE lti_context
  ADD CONSTRAINT lti_context_context_FK1 FOREIGN KEY (primary_consumer_key, primary_context_id)
   REFERENCES lti_context (consumer_key, context_id);

ALTER TABLE lti_user
  ADD CONSTRAINT lti_user_context_FK1 FOREIGN KEY (consumer_key, context_id)
   REFERENCES lti_context (consumer_key, context_id);

ALTER TABLE lti_nonce
  ADD CONSTRAINT lti_nonce_consumer_FK1 FOREIGN KEY (consumer_key)
   REFERENCES lti_consumer (consumer_key);

ALTER TABLE lti_share_key
  ADD CONSTRAINT lti_share_key_context_FK1 FOREIGN KEY (primary_consumer_key, primary_context_id)
   REFERENCES lti_context (consumer_key, context_id);

An optional prefix may be added to the table names. For example, if a prefix of "MyApp_" is specified the expected table names would be MyApp_lti_consumer, MyApp_lti_context etc.

Class definitions

The following classes are defined:

The classes are fully described in the JavaDocs documentation.

Usage

A quick way to see how LTI can be used to integrate an external tool with a learning environment is to watch the following screencast showing how it is being used with WebPA, a peer assessment tool. Note that, whilst this uses Blackboard Learn 9 as the learning environment, the same integration is possible with any other which provides support for the unofficial extensions, this includes WebCT, Moodle, Sakai.
Video View movie illustration - An instructor's view of LTI (10:46 minutes)

Having a learning application hosted outside the learning environment also introduces opportunities for users coming from different resource links to share the same working area within the external tool. This is illustrated by the following screencast which shows how students from different resurce links (which could be from different learning environments and/or different institutions) can be joined together to work on the same peer assessment exercise.
Video View movie illustration - Using LTI to allow student collaboration (6:13 minutes)

The Rating tool provider web application is also available as a simple example of how to use these classes.

Using the Java classes

The classes should be may available for use in a Java file by importing the required classes; for example:

import org.oscelot.lti.tp.ToolProvider;
import org.oscelot.lti.tp.DataConnector;
import org.oscelot.lti.tp.dataconnector.JDBC;

Specifying a data connector

Data is persisted by the classes via a subclass of the DataConnector class. There are two elements to specifying a data connector:

The following code snippet illustrates how to set up a data connector using a JDBC connection string to a MySqL database with a table name prefix of "MyApp_".

import java.sql.DriverManager;
import java.sql.Connection;

import org.oscelot.lti.tp.DataConnector;
import org.oscelot.lti.tp.dataconnector.JDBC;

Connection connection = null;

try {
// Load driver
  Class.forName("com.mysql.jdbc.Driver").newInstance();
// Obtain connection
  connection = DriverManager.getConnection("jdbc:mysql://localhost/AppDb?user=db_user&password=db_pass");
} catch (ClassNotFoundException e) {
} catch (InstantiationException e) {
} catch (IllegalAccessException e) {
}

DataConnector dataConnector = new JDBC("MyApp_", connection);

No data connector

To use the classes without any data persistence use the following:

import org.oscelot.lti.tp.DataConnector;
import org.oscelot.lti.tp.dataconnector.None;

DataConnector dc = new None();

Initialising a tool consumer

When a launch request is received it will be validated with the shared secret associated with the consumer key. The default data structure uses the lti_consumer table to record details of tool consumers. A record may be initialised as follows:

import org.oscelot.lti.tp.ToolConsumer;

ToolConsumer toolConsumer = new ToolConsumer("testing.edu", dataConnector, false);
toolConsumer.setName("Testing");
toolConsumer.setSecret("ThisIsASecret");
toolConsumer.save();

By default, a tool consumer instance is disabled (i.e. will not be used to accept any incoming requests). A tool consumer may be enabled when it is created or updated as follows:

import org.oscelot.lti.tp.ToolConsumer;

ToolConsumer toolConsumer = new ToolConsumer("testing.edu", dataConnector, false);
if (toolConsumer.getCreated() != null) {
  toolConsumer.setEnabled(true);
  toolConsumer.save();
}

Validating a launch request

The primary use case for the classes is to validate an incoming launch request from a tool consumer. Once a record has been initialised for the tool consumer (see above), the verification of the authenticity of the LTI launch request is handled automatically by the ToolProvider class; a callback is made for processing the request if it is valid (see below). For example, if the instance of the callback class is named "DoLaunch" the following code will check the incoming request (the request and response parameters are from the HTTP connection).

import org.oscelot.lti.tp.Callback;
import org.oscelot.lti.tp.ToolProvider;

Callback doLaunch = new DoLaunch();

ToolProvider toolProvider = new ToolProvider(request, response, doLaunch, dataConnector);
toolProvider.setParameterConstraint("user_id", true, 50);
toolProvider.setParameterConstraint("roles", true, null);
toolProvider.execute();

public class DoLaunch implements Callback {

  @Override
  public boolean execute(ToolProvider toolProvider) {

    boolean ok;

// Insert code here to handle incoming connections - use the user
// and resource_link properties of the toolProvider parameter
// to access the current user and resource link.

    return ok;

  }

}

The above example also includes automatic checks for the user_id and roles parameters which will ensure that the both are received in the launch request and the former is no longer than 50 characters. The execute method checks the authenticity of the incoming request by verifying the OAuth signature (using the shared secret recorded for the tool consumer), the timestamp is within a defined limit of the current time, the nonce value has not been previously used, and all required parameters are present. Only if the request passes all these checks is the callback class executed. The following parameters are automatically saved for future use in the ResourceLink object when a launch request is executed with the LTI services:

(The ResourceLink methods of getSetting, setSetting and saveSettings may also be used with user-defined setting values.)

When a launch is not valid, a message is returned to the tool consumer ("Sorry, there was an error connecting you to the application."); this message can be changed in the ToolProvider class file. If a custom parameter of debug=true is included in the launch then a more precise reason for the failure is returned:

If you wish to handle an invalid launch request in a different way, then a error callback method can also be declared. In this case both the launch and error callback methods must be passed to the ToolProvider instance. For example, if the error callback class is named "DoError", the following code will make both callbacks available for use with valid and invalid launch requests:

import java.util.Map;
import java.util.HashMap;

import org.oscelot.lti.tp.Callback;
import org.oscelot.lti.tp.ToolProvider;

Map callbacks = new HashMap();
callbacks.put("connect", new DoLaunch());
callbacks.put("error", new DoError());

ToolProvider toolProvider = new ToolProvider(request, response, callbacks, dataConnector);
...

Protecting a consumer key

The connection between a tool consumer and a tool provider is secured using a consumer key and a shared secret. However, there are some risks to this mechanism:

  1. launch requests will continue to be accepted even if a licence as expired;
  2. if the consumer key is used to submit launch requests from more than one tool conumser, there is a risk of clashing resource link and user IDs being received from each.

The first risk can be avoided by manually removing or disabling the consumer key as soon as the licence expires. Alternatively, the dates for any licence may be recorded for the tool consumer so that the appropriate check is made automatically when a launch request is received.

The second risk can be alleviated by setting the protected property of the tool consumer. This will cause launch requests to be only accepted from tool consumers with the same tool_consumer_guid parameter. The value of this parameter is recorded from the first launch request received using the associated consumer key. Note that this facility depends upon the tool consumer sending a value for the tool_consumer_guid parameter and each tool consumer having a unique value for this parameter.

The following code illustrates how these options may be set:

import java.util.Calendar;

// load the tool consumer record
ToolConsumer toolConsumer = new ToolConsumer("testing.edu", dataConnector, false);

// set an expiry date for 30 days time
Calendar cal = Calendar.getInstance();
cal.add(Calendar.DAY_OF_MONTH, 30);
toolConsumer.setEnableUntil(cal);

// protect use of the consumer key to a single tool consumer
toolConsumer.setProtect(true);

// save the changes
toolConsumer.save();

Note that the default value of the enable_from property is NULL which means that access is available immediately. A NULL value for the enable_until property means that access does not automatically expire (this is also the default).

Callback classes

A callback class is associated with the launch action according to the instance passed to the constructor of the ToolProvider object. The ToolProvider object is passed into the execute method of this class with the resource link and user properties populated with values received via the launch request.

public class DoLaunch implements Callback {

  @Override
  public boolean execute(ToolProvider toolProvider) {

    boolean ok;

// Get consumer key
    String consumerKey = toolProvider.getConsumer().getKey();

// Get user ID
    String userId = toolProvider.getUser().getId();

// Get resource link ID
    String resourceLinkId = toolProvider.getResourceLink().getId();

// ...

    return ok;

  }

}

The callback function may be used to:

To reject a valid launch request use code like the following:

public class DoLaunch implements Callback {

  @Override
  public boolean execute(ToolProvider toolProvider) {

    boolean ok;

// ...

    toolProvider.setReason("Incomplete data");
    ok = false;

    return ok;

  }

}

Services

The ResourceLink class provides support for the following services:

These services are unofficial extensions of Basic LTI (LTI 1.0). An Outcomes service is also included in the LTI 1.1 specification; the classes support both versions of the Outcomes service.

In order to submit a service request, the relevant ResourceLink object is required; this object is identified by the consumer key and resource link ID:

ToolConsumer toolConsumer = new ToolConsumer(consumerKey, dataConnector);
ResourceLink resourceLink = new ResourceLink(toolConsumer, resourceLinkId);

Outcomes service

A grade book for a specific user is identified by their associated result sourcedid; these values are passed when the user launches the tool and are automatically retained by the classes for future use. The result sourcedid for the current user could be saved as a session variable as part of the callback function on launch. Alternatively, the value may be obtained by loading the User record from its user ID:

User user = new User(resourceLink, userId);

The user's grade may be retrieved from the grade book in the tool consumer using a read operation:

Outcome outcome = new Outcome();
if (resourceLink.doOutcomesService(ResourceLink.EXT_READ, outcome, user)) {
  String score = outcome.getValue();
}

The user's grade may be saved to the grade book in the tool consumer using a write operation:

Outcome outcome = new Outcome(score);
boolean ok = resourceLink.doOutcomesService(ResourceLink.EXT_WRITE, outcome, user);

The score should normally be a decimal value between 0 and 1, but the doOutcomesService method makes every effort to convert the value passed to a format which is accepted by the tool consumer; for example, a percentage of 65% may be converted to a decimal 0.65. The delete operation can be used to remove a grade from the grade book.

Memberships service

The doMembershipsService() method allows a tool provider to request a list of users with access to the resource link from the tool consumer. The method returns a list of User objects.

List users = resourceLink.doMembershipsService();

Setting service

The doSettingService(action) and doSettingService(action, value) methods are used to retrieve, save and delete data in the tool consumer resource link.

String setting = resourceLink.doSettingService(ResourceLink.EXT_READ);

The above line is equivalen to:

String setting = "";
boolean ok = resourceLink.doSettingService(ResourceLink.EXT_READ, null);
if (ok) {
  setting = resourceLink.getSetting("ext_ims_lti_tool_setting");
}
String setting = "Please remember this ...";
boolean ok = resourceLink.doSettingService(ResourceLink.EXT_WRITE, setting);
boolean ok = resourceLink.doSettingService(ResourceLink.EXT_DELETE);

Version history

VersionDateDescription
1.0.002 January 2013Initial release
1.1.0013 April 2013 Added option for error callback method
Updated to support latest release of OAuth class library
1.1.0118 June 2013 Altered order of checks in authenticate
Fixed bug with not updating a resource link before redirecting to a shared resource link
Fixed bug causing user records not to be saved
Tightened up setting of roles - now case sensitive and use fully qualified URN
Fixed bug with OutcomesService when a resource link is shared with a different tool consumer
Separated User from Outcome object

Licence

GNU Lesser General Public License This work is written by Stephen Vickers and is released under a GNU Lesser General Public Licence. The LTI Tool Provider package is available for download from OSCELOT where it is also possible to report bugs and submit feature requests.

Valid XHTML 1.0 Strict