Introduction

Cisco Context Service is a cloud-based omnichannel solution for Cisco Contact Center Express and Contact Center Enterprise.

Context Service provides the ability to store contextual data about customer interactions in the cloud and make that data available to other applications and services.

Use the Context Service Java SDK to integrate Cisco Context Service with your application. This guide serves as a companion to the official Context Service SDK Javadoc.

Release Notes

September 2017

Version 2.0.3 (current version)

January 2017

Version 2.0.1

Updated Features

You can now specify doubles and 32-bit integers as data types for fields. See Define Fields and Fieldsets for more information.

October 2016

Version 2.0.1

Updated Features

In this version you can no longer create publicly accessible fields. This prevents collision with your organization's private fields.

September 2016

Version 2.0.1

In This Version

New Features

  • This version introduces proxy support with the new property contextservice.proxyURL. To get changes, reinitialize the client:

    updateAndReloadConfigAsync()

See Connect to Context Service for more information.

  • You can get the latest contributor with the new method Contributor getLastContributor().

  • When you initialize the SDK, the automatic upgrade check blocks the init method until the check is complete and the upgrade, if needed, is complete.

  • Newly created and updated PODs are not visible to the search method immediately after you save the POD. This can cause Read POD Step/node to return stale results. If you need to retrieve PODs as soon as you save, use get POD.

  • You can now configure Context Service SDK to use a different logger. For more information, see SDK Client Logging.

  • You can now flush data created in lab mode before you set up your organization. For more information, see Flush all Object Data

Updated Features

  • Your connection state will no longer change from Registered to Stopped during automatic upgrades. Only connector events like destroy can cause state changes. For more information in connections states, see Monitoring Connection State.

  • The context-service-sdk.jar file now has fewer dependencies, which allow the use of various libraries and library versions. The change also removes some restrictions imposed in the earlier versions of the SDK. This change results in a modified SDK with these changes:

    • Connector configuration properties are now supplied to the ContextServiceClient.init() method using an instance of the com.cisco.thunderhead.connector.ConnectorConfiguration class instead of the Apache Configuration class.
    • ClientResponse class used as the return type from ContextServiceClient interface methods including create() and update() is now located in the com.cisco.thunderhead.client package and not the com.sun.jersey.api.client.
    • ContextServiceClient.search() method now uses com.cisco.thunderhead.client.SearchParameters class. This class extends Map<String, List<String>> and replaces Map<String, String> and MultiValueMap<String, String>.
    • You will no longer see case-specific derived exception class. The only exception class is ApiException. Use the error type member of this class to see other case specific exceptions.

Deprecated or Removed Features in Context Service SDK Version 2.0.1

These methods have been removed or changed in Context Service SDK Version 2.0.1:

Removed Method Removed Method Result Valid Method Compatibility Notes
setContributors(List ) Errors setNewContributor() Replace all instances of setContributors(List ) with setNewContributor(). See Add New Contributer to an Activity
encryptAndCreate Errors create() Replace all instances of encryptAndCreate with create().
encryptAndUpdate() Errors update() Replace all instances of encryptAndUpdate() with update().
getAndDecrypt Errors get() Replace all instances of getAndDecrypt with get().
searchAndDecrypt() Errors search() Replace all instances of searchAndDecrypt() with search().
Request.getPods() Does not return any results search() by POD ID Change code with Request.getPods() to search for activities (PODs) using POD Ids.

Context Service SDK Components

The Context Service Java SDK provides:

  • Static SDK jar file—This file contains all classes and methods used to integrate with Context Service. Your application has a compile-time dependency on this file. Include this file in your application classpath. The SDK is designed to dynamically update the extension jar using the connector property file.

  • Dynamic SDK extension jar file—This file contains the implementation of the static jar API. The dynamic jar file must be saved outside your application classpath. The dynamic jar file exists outside the classpath in order to isolate dependencies from your application. The extension jar file is dynamically updated at application runtime.

  • connector.property file—Property file specifying name and location of the extension jar file.

  • Context Service SDK POM file—POM file that lists maven dependencies for the SDK.

The connector.property file contains the name and path of the extension SDK jar file. At your application's runtime, the static SDK uses the connector.property file to access and dynamically update the extension SDK.

The static or base SDK jar file contains all classes and methods used to integrate with Context Service. Your application has a compile-time dependency on this file. Include this file to your application classpath. The SDK is designed to dynamically update the extension jar. The Extension jar implements the updates, which enable additional functionality and issue resolutions with no change in your code.

The extension SDK jar file is stored outside the application classpath. This ensures that the integrity of your application dependencies are maintained even during updates and prevents conflicts between your application dependencies. The static or the base JAR dynamically loads the extension JAR at runtime and automatically checks the version of the extension JAR file. If a newer version of the extension JAR is available, the management connection automatically downloads and uses it. The connector.property file is saved by default to the root resource directory of your application. The static SDK jar file uses the connector.property file to look up the location and dynamically update the extension SDK. If the connector.property file is not in the default location, you can manually load the connector.property file.

Initialize Connector Factory

The connector factory must be initialized:

  • Before initializing the client connector.

  • Before initializing the management connector.

  • At application runtime.

If the connector.property file is not in the default location, then you must manually initialize the factory with the correct path to connector.property. This example shows how to initialize the connector factory:

/**
 * Before you initialize the connectors, initialize ConnectorFactory.
 */
public static void initializeConnectorFactory() {
    String pathToConnectorProperty = Paths.get("./connector.property").toAbsolutePath().toString();
    ConnectorFactory.disableBootstrapUpgradeCheck();
    ConnectorFactory.initializeFactory(pathToConnectorProperty);
}

Update Extension Jar Automatically

The extension SDK does not have a compile-time dependency with the base SDK. This enables your application to dynamically update the extension SDK at runtime. When your application starts, the management connection functionality automatically checks the version of the SDK and updates to the latest available extension JAR.

To allow the extension JAR to update automatically, edit the connector.property file and set the appropriate write privileges to the directory that contains the extension JAR.

The location of the SDK extension JAR is specified in the connector.property file. By default, the base SDK looks for connector.property in the same directory from which the application is launched.

connector.property contains two properties:

  • path—Path to the extension SDK.
  • jar-name—Name of the extension jar file.

For example: path=plugin jar-name=context-service-sdk-extension-2.0.1.jar

The path property must specify a path that the Java process can write to, and cannot be in the Java CLASSPATH. The Java process must also be able to write to the extension JAR file to enable automatic updates.

Once your application has the latest SDK extension you can connect to Context Service.

SDK Client Logging

The Context Service SDK logs diagnostic messages to the SLF4J interface. By default, the SDK includes the logback binding for SLF4J. Use logback for logging or configure a different SLF4J-compatible logger.

Configure Logback

Include logback.xml in your class path to enable Logback to detect your configuration. For more information on Logback, see the Logback manual.

Configure New Logger

To configure Context Service SDK to use a different SLF4J-compatible logger than the default Logback:

  • Exclude Logback-classic from your project.
  • Include SLF4J bindings for the new logger.
  • Include the new logger.
<dependency>
    <groupId>com.cisco.thunderhead</groupId>
    <artifactId>context-service-sdk</artifactId>
    //Exclude Logback-classic binding.
    <exclusions>
        <exclusion>
            <groupId>ch.qos.logback</groupId>
            <artifactId>logback-classic</artifactId>
       </exclusion>
    </exclusions>
</dependency>

//Include log4j binding for slf4j.
<dependency>
    <groupId>org.slf4j</groupId>
    <artifactId>slf4j-log4j12</artifactId>
    <version>1.7.21</version>
</dependency>

//Include log4j Core logger.
<dependency>
    <groupId>org.apache.logging.log4j</groupId>
    <artifactId>log4j-core</artifactId>
    <version>2.6</version>
</dependency>

To learn more about configuring your new logger with SLF4J, see the SLF4J documentation.

Register Your Application with Context Service

You must register your custom application with Context Service. Registration creates the connection data your applications require to authenticate and connect to Context Service. Connection data contains the credentials and URLs that allow your application to connect to Context Service in the Cisco cloud.

Registration allows a specific application or a cluster to connect to Context Service. For example, for deployments of Unified CVP, Finesse, and a custom Context Service application, you register three separate times:

  • Register the CVP cluster
  • Register the Finesse cluster
  • Register the custom application

Registration creates a machine account in the cloud, tied to the connection data associated with the registration. Any context data written or modified by the machine account is noted as such in a contributor list for the data object.

Registration Workflow

To register Context Service you must:

  1. Obtain an account that is entitled for Context Service.
  2. Generate a registration request.
  3. Authorize registration of your application.

Context Service Entitlement

To use Context Service, you need a Cisco Spark Control Hub account that is entitled for Context Service. Contact your account representative to create an account and provision it for use with Context Service.

Generate Registration URL

Generating a Registration URL requires:

  • Callback URL—The address for a service that you host (generally local to the application) to capture the connection data sent back from Cisco Collaboration Cloud Management. If your application is a web service, then set up a new page to capture the Connection Data. Optionally, use a local embedded Jetty server to host a page that captures the connection data.
  • Application Type—The type of client application you are registering. All applications must provide an application type.
    • If you have an assigned application type: use your assigned application type.
    • If you don't have an assigned application type: use custom as your application type.

To generate a Registration URL:

  • Get RegistrationApplication connector from ConnectorFactory.
  • Get RegistrationUrl by creating a registration request.

This example shows how to generate a registration URL.

/**
 * Follow the registration workflow on https://developer.cisco.com/site/context-service/documents/context-service-sdk-guide/index.gsp#registration-workflow
 * to authorize your applications for Context Service.
 * 
 * Prerequisite:
 * 1. A Callback Url: web page hosted in your application server or local jetty server to capture connection data.
 *
 * @return a registration url
 */
public static String generateRegistrationUrl(){
    String registrationURL = null;
    try {
        String callbackUrl = "https://localhost:7443/productCallback";
        String APPLICATION_TYPE = "custom";

        //Instantiating RegistrationApplication connector from ConnectorFactory
        RegisteringApplication registerApp = ConnectorFactory.getConnector(RegisteringApplication.class);

        //Making a registration request for `custom` application type
        registrationURL = registerApp.createRegistrationRequest(callbackUrl, APPLICATION_TYPE);
        LOGGER.info("\n \n *** Generated registration url. Open Registration URL: " + registrationURL);

    } catch (ApiException e) {
        LOGGER.info("Error generating Registration Url! The Error is:" + e);
    }

    return registrationURL;
}

Authorize Registration of Your Application

Authorizing Registration requires:

  • Registration URL

To authorize registration:

  1. Get a Cisco Spark Control Hub account that is entitled with Context Service.
  2. Open your generated Registration URL in a browser (Firefox Recommended).
  3. Log into Cisco Spark Control Hub using your organization admin account credentials.
  4. Click Allow to allow your application to access Context Service.

Your browser is redirected to the callback URL defined when you generated the registration URL. Your application stores the connection data returned by the registration process on this page. The registration process is complete once you see connectionData in the callback URL similar to:

https://localhost:7443/initClient?connectionData=VeryLongString...

For testing purposes, you do not need to host a web server at your callback URL. Instead, copy the connection data string from the URL returned by authorizing registration.

Save the connectionData string in your source code and use it for all connections to Context Service.

Deregister your Application

Deregistering stops your service and closes the connection. Once you deregister, the connection state is changed to unregistered and all accounts in your connection data are deleted from Common Identity. When unregistered, you can no longer authenticate or communicate with Context Service.

Deregistering your Application requires:

  • The ConnectorState of the Context Service management connector must be Registered.
  • A valid callback URL. The callback URL can be a webpage hosted in your application server, or a local jetty server. This URL receives the response of your deregistration request.

Deregistering uses the callback URL to receive the deregistrationUrl. This deregistrationUrl is used to deregister your application by sending the deregistration request status back to your application.

To stop the connection without deregistering your service, use destroy. For more information, see Stop Context Service.

To deregister your application:

  1. Make a valid connection using the management connector, then call deregister().
  2. Pass a callback URL to deregister(). This returns a deregistrationUrl.
  3. Open the deregistrationUrl in a browser (Firefox Recommended).

This example shows how to generate the deregistrationUrl used to deregister Context Service.

/**
 * Follow the deregistration workflow on https://devnet-stage.developerprogram.org/site/context-service/documents/context-service-sdk-guide/#deregister-your-application
 *
 * Prerequisite
 * 1. A Callback Url: webpage hosted in your application server or local jetty server to receive response of deregistration request.
 *
 * @param managementConnector a ManagementConnector instance in a `Registered` ConnectorState
 * @return a deregistration url
 */
public static String generateDeregistrationUrl(ManagementConnector managementConnector){
    String deregistrationURL = null;
    try {
        // callbackUrl will be used to receive response of the generate de-registration url request
        String callbackUrl = "https://localhost:7443/productCallback";
        deregistrationURL = managementConnector.deregister(callbackUrl);
        LOGGER.info("\n \n *** Generated deregistration url. Open This URL: " + deregistrationURL);
    } catch (ApiException e) {
        LOGGER.info("Error creating de-registration Url! The Error is: " + e);
    }

    return deregistrationURL;
}

Stop Context Service

To temporarily stop using Context Service in your application, call the destroy method on the Management Connector and Client Connector. This changes the ConnectorState to STOPPED. Calling destroy will not deregister your service or delete your connection data. To resume service, re-initialize the Management Connector and Client Connector using your original connection data.

Connect to Context Service

Network Connectivity Requirements

Context Service is a public Internet service that operates on port 443 (HTTPS). Context Service connects to servers at the following URLs:

  • *.webex.com
  • *.wbx2.com
  • *.ciscoccservice.com

Proxy Configuration

Context Service supports a HTTP proxy server. If your application does not have a direct connection to the Internet, then you can connect to Context Service through a proxy. The proxy server is configured through the property contextservice.proxyURL.

The proxy setting is a Java property that allows you to set the proxy by:

  • Hard coding the proxy setting in your code:

System.setProperty("contextservice.proxyURL","http://<proxy_host>:<port_number>" )

  • Setting the proxy when you start your application with a JVM Argument:

java -jar myApp.jar -Dcontextservice.proxyURL=http://<proxy_host>:<port_number>

Context Service Connections

After your application is registered with Cisco Spark Control Hub your application creates two connections to the Cisco Cloud using your connection data:

  • Context Service Management Connection—Reports the connection status to Cisco Spark Control Hub and automatically refreshes the internal password for your connection data.
  • Context Service Client Connection—Uses the Context Service client with your application.

Both connections require you to authenticate by providing your connection data.

Typically, you create a single management connection and have one or more client connections that connect to Context Service.

Cisco Spark Control Hub requires that the internal password associated with the connection data is renewed every 270 days. The Management Connector is responsible for keeping the connection data valid. When the internal password for the connection data changes, the management connector fires a listener that you use to update the connection data for all the clients associated with this Management Connector's connection data.

Create Connection Listeners

Listeners allow your application to listen for a connection change, then perform an action.

Create State Listeners

Use the addStateListener() method to create a state listener before initializing the management connector and the Context Service Client connector. This allows your application to act on a state change.

The three states are coded as constants in the Context Service SDK:

  • ConnectorState.STOPPED
  • ConnectorState.REGISTERED
  • ConnectorState.UNREGISTERED

This example shows how to create a state listener for the management connector. The function takes managementConnector as the input parameter and returns the created state listener.

/**
 * Before initializing the connector, create and add a ConnectorStateListener to the ManagementConnector.
 * @param managementConnector
 * @param isRegistered
 * @return the created ConnectorStateListener
 */
public static ConnectorStateListener addStateListenerToManagementConnectorWithFlag(ManagementConnector managementConnector, final AtomicBoolean isRegistered){
    ConnectorStateListener stateListener = new ConnectorStateListener() {
        public ConnectorState connectorState;

        @Override
        public void stateChanged(ConnectorState previousState, ConnectorState newState)
        {
            connectorState = newState;
            LOGGER.info("Management Connector state changed: " + newState);
            if (newState == ConnectorState.STOPPED) {
                // Perform optional cleanup tasks; update state related application flags
                if(null!= isRegistered)
                    isRegistered.set(false);

                LOGGER.info("Management Connector stopped.");
            }else if (newState == ConnectorState.REGISTERED) {
                // Perform any actions needed once connector is registered; update state related application flags
                if(null!= isRegistered)
                    isRegistered.set(true);

                LOGGER.info("Management Connector registered.");
            }
        }
    };
    managementConnector.addStateListener(stateListener);
    return stateListener;
}

This example shows how to create a state listener for the Context Service client connector. The function takes contextServiceClient as the input parameter and returns the created state listener.

/**
 * Before initializing the connector, create and add a ConnectorStateListener to the ContextServiceClient.
 * @param contextServiceClient
 * @return the created ConnectorStateListener
 */
public static CustomConnectorStateListener addStateListenerToContextConnector(ContextServiceClient contextServiceClient){
    CustomConnectorStateListener stateListener = new CustomConnectorStateListener();
    contextServiceClient.addStateListener(stateListener);
    return stateListener;
}

Create Credentials Listeners

Use the addCredentialsChangedListener() method to create a credentials listener before initializing the management connector. This allows your application to act on a credentials change.

This example shows how to create a credentials listener for the management connector. The function takes managementConnector as the input parameter and returns the created credentials listener.

/**
 * Create and add a CredentialsChangedListener to the ManagementConnector.
 * It's recommended that you do this before initializing the connector.
 * @param managementConnector
 * @return the created CredentialsChangedListener
 */
public static CredentialsChangedListener addCredentialsListenerToManagementConnector(ManagementConnector managementConnector, final ContextServiceClient contextServiceClient){
    CredentialsChangedListener credentialsChangedListener = new CredentialsChangedListener() {
        String connectionData;

        @Override
        public void credentialsChanged(String newConnectionData) {
            LOGGER.info("ConnectionData changed: " + newConnectionData);
            connectionData = newConnectionData;
            // Connection data is not usually logged due to security considerations.

            String hostname = "doctest.example.com";
            ConnectorInfoImpl connInfo = new ConnectorInfoImpl(hostname);
            ConnectorConfiguration configuration = new ConnectorConfiguration() {{
                addProperty("LAB_MODE", true); // exclude this line for production mode
                addProperty("REQUEST_TIMEOUT", 10000);
            }};
            // Notify contextServiceClient that the connection data changed.
            contextServiceClient.updateAndReloadConfigAsync(connectionData, connInfo, configuration, null);
        }
    };
    managementConnector.addCredentialsChangedListener(credentialsChangedListener);
    return credentialsChangedListener;
}

Management Connection

Before you create a Context Service management connection, you must:

Before you initialize your management connector, optionally:

To create a Context Service management connection:

  1. Create and initialize a ManagementConnector object and call ConnectoryFactory.getConnector() on the ManagementConnector.class.

  2. Create a connection state listener using the addStateListener() method on the management connector.

  3. Create a listener class and add it as a CredentialsChangeListener. This listener class is used to update the ConnectionData string if the credentials need to change based on a machine password reset requirement.

  4. Initialize the connection using init. You must specify the connection data for authentication and your application's host name. The host name is used to display your applications status in Cisco Spark Control Hub.

This example shows how to create and initialize the management connector while using a connection state listener and a credentials listener.

/**
 * Create and initialize the ManagementConnector with custom configuration.
 * ConnectorFactory should already be initialized.
 * @param connectionData
 * @return an initialized ManagementConnector
 */
public static ManagementConnector createAndInitManagementConnectorWithCustomConfiguration(String connectionData){
    AtomicBoolean isRegistered = new AtomicBoolean(false);

    ManagementConnector managementConnector = ConnectorFactory.getConnector(ManagementConnector.class);
    // Add Management connector state listener. It needs to be done before calling init on the connector
    mgmtConnectorStateListener = addStateListenerToManagementConnectorWithFlag(managementConnector, isRegistered);

    String hostname = "doctest.example.com";
    ConnectorInfoImpl connInfo = new ConnectorInfoImpl(hostname);
    ConnectorConfiguration configuration = new ConnectorConfiguration(){{
        addProperty("LAB_MODE", true); // exclude this line for production mode
        addProperty("REQUEST_TIMEOUT", 10000);

    }};
    managementConnector.init(connectionData, connInfo, configuration);

    // Optionally, parse the JSON returned by getStatus for additional status information
    String status = managementConnector.getStatus();
    // Connector could be already registered in Before Class, so check if it is already registered
    if(! status.contains("REGISTERED")) {
        try {
            waitForConnectorToRegister(isRegistered, 3);
            LOGGER.info(">>>> Management Connector initialized successfully");
        } catch (Exception e) {
            LOGGER.error(">>>> Management Connector FAILED to initialize successfully", e);
        }
    }

    return managementConnector;
}
/**
 * Before initializing the connector, create and add a ConnectorStateListener to the ManagementConnector.
 * @param managementConnector
 * @param isRegistered
 * @return the created ConnectorStateListener
 */
public static ConnectorStateListener addStateListenerToManagementConnectorWithFlag(ManagementConnector managementConnector, final AtomicBoolean isRegistered){
    ConnectorStateListener stateListener = new ConnectorStateListener() {
        public ConnectorState connectorState;

        @Override
        public void stateChanged(ConnectorState previousState, ConnectorState newState)
        {
            connectorState = newState;
            LOGGER.info("Management Connector state changed: " + newState);
            if (newState == ConnectorState.STOPPED) {
                // Perform optional cleanup tasks; update state related application flags
                if(null!= isRegistered)
                    isRegistered.set(false);

                LOGGER.info("Management Connector stopped.");
            }else if (newState == ConnectorState.REGISTERED) {
                // Perform any actions needed once connector is registered; update state related application flags
                if(null!= isRegistered)
                    isRegistered.set(true);

                LOGGER.info("Management Connector registered.");
            }
        }
    };
    managementConnector.addStateListener(stateListener);
    return stateListener;
}
/**
 * Wait for the connector state to be REGISTERED or throw an exception if timeoutSec value is reached.
 *
 * This example shows how to wait for the REGISTERED state by checking a connector state application flag
 * which is updated in the stateListener.
 * @param isRegistered
 * @param timeoutSec
 * @throws Exception
 */
public static void waitForConnectorToRegister(AtomicBoolean isRegistered, int timeoutSec) throws Exception{
    long startTime = System.currentTimeMillis();
    while((System.currentTimeMillis() - startTime) <= timeoutSec*1000 &&
            !isRegistered.get()){
        try {
            Thread.sleep(100);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
    if(!isRegistered.get()){
        throw new Exception("Timed out waiting for connector to register.");
    }
}
/**
 * Create and add a CredentialsChangedListener to the ManagementConnector.
 * It's recommended that you do this before initializing the connector.
 * @param managementConnector
 * @return the created CredentialsChangedListener
 */
public static CredentialsChangedListener addCredentialsListenerToManagementConnector(ManagementConnector managementConnector, final ContextServiceClient contextServiceClient){
    CredentialsChangedListener credentialsChangedListener = new CredentialsChangedListener() {
        String connectionData;

        @Override
        public void credentialsChanged(String newConnectionData) {
            LOGGER.info("ConnectionData changed: " + newConnectionData);
            connectionData = newConnectionData;
            // Connection data is not usually logged due to security considerations.

            String hostname = "doctest.example.com";
            ConnectorInfoImpl connInfo = new ConnectorInfoImpl(hostname);
            ConnectorConfiguration configuration = new ConnectorConfiguration() {{
                addProperty("LAB_MODE", true); // exclude this line for production mode
                addProperty("REQUEST_TIMEOUT", 10000);
            }};
            // Notify contextServiceClient that the connection data changed.
            contextServiceClient.updateAndReloadConfigAsync(connectionData, connInfo, configuration, null);
        }
    };
    managementConnector.addCredentialsChangedListener(credentialsChangedListener);
    return credentialsChangedListener;
}

Client Connector

Before you create a Context Service Client connection, you must:

Before you initialize the Context Service client connection, optionally test your connection using getStatus. See Check Connection State for more information.

To create a Context Service client connection:

  1. Define a ContextServiceClient object and call ConnectoryFactory.getConnector() on the ContextServiceClient.class.

  2. (Optional) Define the base configuration for your client by creating a ConnectorConfiguration object and add properties. Create a base configuration only to change the default configuration values. The default values are:

    Property Description Default Value
    LAB_MODE Specify if the organization is in lab mode. false. To set Organization to lab mode, change value to true.
    REQUEST_TIMEOUT Specify the amount of time the method waits before the request times out. Methods on which you can timeout include create,get, and search. 10,000ms
    TCP_TIMEOUT Specify the timeout for Request made at the REST-API level. You cannot set the TCP timeout value to less than the value of the request (method level) timeout. 10,000ms

    The "LAB_MODE" configuration property sets the mode for your organization and is false by default. Context Service currently supports two modes per organization, Production and Lab. You cannot delete objects in Production mode. When you are developing your application, set the lab_mode field to true. This allows you to delete your example data. See Flush Object Data for more information.

  3. Create a connection state listener using the addStateListener() method on the Context Service client connector.
  4. Initialize the client with the init method.

This example shows how to create and initialize a Context Service Client connector while using a connection state listener.

/**
 * Create and initialize the ContextServiceClient with custom configuration.
 * ConnectorFactory should already be initialized.
 * @param connectionData
 * @return an initialized ContextServiceClient
 */
public static ContextServiceClient createAndInitContextServiceClientWithCustomConfiguration(String connectionData) {
    ContextServiceClient contextServiceClient = ConnectorFactory.getConnector(ContextServiceClient.class);

    // Add Context Service Client connector state listener. It needs to be done before calling init on the connector
    connectorStateListener = addStateListenerToContextConnector(contextServiceClient);

    String hostname = "doctest.example.com";
    ConnectorInfoImpl connInfo = new ConnectorInfoImpl(hostname);
    ConnectorConfiguration configuration = new ConnectorConfiguration(){{
        addProperty("LAB_MODE", true); // exclude this line for prod mode
        addProperty("REQUEST_TIMEOUT", 10000);

    }};
    contextServiceClient.init(connectionData, connInfo, configuration);

    // Wait 3 sec for connector to be initialized.
    try {
        waitForConnectorState(connectorStateListener, ConnectorState.REGISTERED, 3);
        LOGGER.info(">>>> CS Connector initialized successfully");
    }catch(Exception e){
        LOGGER.error(">>>> CS Connector FAILED to initialize successfully", e);
    }

    // Optionally, parse the JSON returned by getStatus for additional status information
    String status = contextServiceClient.getStatus();

    return contextServiceClient;
}
/**
 * Before initializing the connector, create and add a ConnectorStateListener to the ContextServiceClient.
 * @param contextServiceClient
 * @return the created ConnectorStateListener
 */
public static CustomConnectorStateListener addStateListenerToContextConnector(ContextServiceClient contextServiceClient){
    CustomConnectorStateListener stateListener = new CustomConnectorStateListener();
    contextServiceClient.addStateListener(stateListener);
    return stateListener;
}
/**
 * Create a Custom Connector State Listener to override the stateChanged behavior
 */
public static class CustomCSConnectorStateListener implements ConnectorStateListener {
    protected ConnectorState connectorState;

    public ConnectorState getConnectorState(){
        return connectorState;
    }

    @Override
    public void stateChanged(ConnectorState previousState, ConnectorState newState)
    {
        connectorState = newState;
        LOGGER.info("Context Service Client connector state changed: " + newState);
        if (newState == ConnectorState.STOPPED) {
            // Perform optional cleanup tasks, etc ...
            LOGGER.info("Context Service Client connector stopped.");
        }else if (newState == ConnectorState.REGISTERED) {
            // Perform any actions needed once connector is registered, etc ...
            LOGGER.info("Context Service Client connector started.");
        } else if (newState == ConnectorState.UNREGISTERED) {
            // Perform any actions needed once connector is unregistered, etc ...
            LOGGER.info("Context Service Client connector unregistered.");
        }
    }
}
/**
 * Wait timeoutSec for connector to reach a specified state.
 *
 * This example shows a different technique using CustomConnectorStateListener.getConnectorState() method to determine
 * connector state changes.
 * @param stateListener
 * @param expectedState
 * @param timeoutSec
 * @throws Exception
 */
public static void waitForConnectorState(CustomCSConnectorStateListener stateListener, ConnectorState expectedState, int timeoutSec) throws Exception{
    long startTime = System.currentTimeMillis();
    while((System.currentTimeMillis() - startTime) <= timeoutSec*1000 &&
            expectedState.equals(stateListener.getConnectorState())){
        try {
            Thread.sleep(100);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
    if(!expectedState.equals(stateListener.getConnectorState())){
        throw new Exception("Timed out waiting for connector to reach "+ expectedState.name()+"; Current state is :" + stateListener.getConnectorState());
    }
}

Update Connection State

To update your connection state call updateAndReloadConfigAsync(). This method allows the client to dynamically update the connector configuration without having to stop and reinitialize the connection state.

An update is required when you add or change information in your configuration file. updateAndReloadConfigAsync() is an asynchronous method. Calling this method does not indicate completion. Insert a reloadListener to listen for completion.

This example shows how to update your connection state.

/**
 * Create and add a ReloadListener to a Connector to update and reload.
 * @param connector the connector to relaod
 * @param connectionData your connectionData string
 * @param connectorInfo a ConnectorInfo object
 * @param connectorConfiguration a ConnectorConfiguration object
 * @return the new reload listener, after a reload has completed
 */
public static ReloadListenerWithWait updateAndReloadConnector(Connector connector, String connectionData, ConnectorInfo connectorInfo, ConnectorConfiguration connectorConfiguration){
    ReloadListenerWithWait reloadListener = new ReloadListenerWithWait();
    connector.updateAndReloadConfigAsync(connectionData, connectorInfo, connectorConfiguration, reloadListener);

    try {
        reloadListener.waitForCompletion(60000);
    } catch (ApiException apiException) {
        if (apiException.getError().getErrorType().equals(ApiErrorType.TIMEOUT_REQUEST)) {
            LOGGER.error("Reload timed out!");
        } else {
            LOGGER.error("Error reloading connector: " + apiException.toString());
        }
        throw apiException;
    }

    return reloadListener;
}

Monitor Connection State

Both the Management and Context Service clients have connection states. The states are:

  • Unregistered—The connector is unregistered:
    • Before the connection has been registered and initialized.
    • After the connector has been deregistered.
  • Registered—The connection is registered when it is running after being initialized. This is the normal state for a working connection.
  • Stopped—The connection is stopped when a registered connection is destroyed using the destroy method. Call init to change the connection state from stopped to registered.

Check Connection State

Use getStatus to return a detailed log of connection state information. Use Connection Listeners to listen for a connection change, then perform an action.

If your connection is unregistered, your connection state is returned as OFFLINE. You can use the lastSavedError and the Service: CCFS JSON information to troubleshoot the connection.

In this example, the connection is offline because the proxy host is unknown. You can check your config settings to correct your proxy specification.

/**
 * Unregistered Connection State
 */
{
   "status":{
      "overallStatus":"OFFLINE",
      "successfulUpgradeCount":0,
      "failureUpgradeCount":0,
      "lastSavedError":"RestApiError with errorType: unknownHostError, errorData: /discovery/apps/v1, errorMessage: invalidproxy.cisco.com"
   },
   "config":{
      "staticSdkVersion":"2.0.1",
      "extensionSdkVersion":"2.0.2",
      "proxy":"http://invalidproxy.cisco.com:80",
      "state":"UNREGISTERED"
   },
   "services":[
      {
         "name":"ccfs",
         "url":"ccfs.ciscoccservice.com",
         "ping":{
            "status":"NOT_REACHABLE",
            "latency":0
         }
      }
   ]
}

This example shows the results of getStatus when the Context Service client is registered and initialized.

/**
 * Registered Connection State
 */
{
  "status": {
        "overallStatus": "ONLINE",
        "successfulUpgradeCount": 0,
        "failureUpgradeCount": 0
    },
  "config": {
    "appType": "finesse",
    "orgId": "11112222-aabb-3333-4444-555666777888"",
    "uuid": "zm1n23456-bv7c-8xz9-0123-lk4j567hgf8d",
    "staticSdkVersion": "2.0.1",
    "extensionSdkVersion": "2.0.2-10318",
    "enabledFeatures": [
      {
        "name": "KMS_ENCRYPTION_KEY"
      }
    ],
    "type": "cs_context",
    "state": "REGISTERED",
    "labMode": true,
    "maxRetries": 1,
    "cluster": {
      "clusterId": "q8gh9sh6-1234-56ab-c7d8-9f012ghj34k5",
      "clusterName": "finesse-context-155DA809CBE"
    }
  },
    "services": [
    {
      "name": "kms",
      "url": "encryption-a.wbx2.com",
      "ping": {
        "status": "200",
        "latency": 357
      },
      "state": "INITIALIZED"
    },
    {
      "name": "fms",
      "url": "hercules-a.wbx2.com",
      "lastSuccessfulHeartBeatTime": "07:11:16 11:09:28",
      "ping": {
        "status": "200",
        "latency": 365
      }
    },
    {
      "name": "dictionary",
      "url": "dictionary.produs1.ciscoccservice.com",
      "ping": {
        "status": "200",
        "latency": 220
      }
    },
    {
      "name": "ccfs",
      "url": "ccfs.ciscoccservice.com",
      "ping": {
        "status": "200",
        "latency": 369
      }
    },
    {
      "name": "discovery",
      "url": "discovery.produs1.ciscoccservice.com",
      "ping": {
        "status": "200",
        "latency": 187
      }
    },
    {
      "name": "ci",
      "url": "idbroker.webex.com",
      "ping": {
        "status": "200",
        "latency": 502
      }
    },
    {
      "name": "context",
      "url": "context-service.produs1.ciscoccservice.com",
      "ping": {
        "status": "200",
        "latency": 221
      }
    }
  ]
}

Destroy Connection State

You can shut down a connection without deregistering or stopping your service. destroy() shuts down a client connection.

To stop your application from authenticating with Context Service, Deregister your application.

Context Service Objects

Context Service uses three main objects to store context data:

  • Customer—Data about a specific customer. A customer object provides a way of linking personally identifiable information (PII) data with a customer ID. PII is personal data such as name and address, phone number, or a link to associated customer details in a different data source.
  • Request—Data about one or more customer interactions for a specific issue. A request object reflects the customer's view of an issue, indexes customer journey and interaction, and is used to group related activities together around the customer goal.
  • Activity—Data about a specific customer interaction or a POD associated with a customer or a request.

These objects belong to the ContextBean class and are managed with the ContextServiceClient() interface. Using this interface, you can create and update objects stored in Context Service. You can also delete objects in Lab Mode.

Data is stored in these objects as fields. Context Service objects can have multiple fieldsets assigned to them. You can use fields and fieldsets available by default or add your own custom fields and fieldsets. See Base Fields and Fieldsets and Custom Fields and Fieldsets.

Each individual Context Service data object is limited to 256KB in size.

Object State

Context Service uses the object state to determine when contributors can modify an object.

  • Customer—State is ACTIVE and cannot be set to CLOSED.
  • Request—State is either ACTIVE or CLOSED.
  • Activity—State is either ACTIVE or CLOSED.

Objects cannot be updated when they are in a CLOSED state. You cannot set an object with a CLOSED state to an ACTIVE state.

You can use the Context Service SDK to set the state or return the current state of activities. Use the setState() method on an activity to set the activity's state as either ACTIVE or CLOSED. Use the getState() method on an activity to return the activity's state.

Context Service provides end-point encryption so that sensitive data is not stored or transported in plain text. When you define a field, you specify how the field classifies data. You can classify data as:

  • Unencrypted—Data is stored as plain text.
  • Personally Identifiable Information (PII)—Data is PII, and is encrypted.
  • Non-PII Encrypted—Data is not PII, and is encrypted.

For examples on how Context Service classifies default fields, see Base Fields and Fieldsets.

You can use the type differentiation of encrypted data for analysis. You can give different levels of access to different classifications of data. For example, you can run analytics on non-PII information that you still want to store as encrypted, and not allow access to the PII data associated with the non-PII data.

You can store unencrypted data for non-confidential objects to enable easier search. Use Context_POD_Activity_link to store a searchable string.

Object Properties

Property Description Value Usage
contributors List of collaborators for current activity. Includes current and historic data. Read-only, automatically updated when user saves or updates activity. Activity
created Object creation time stamp. Read-only, generated on creation. Activity, Customer, Request
customerId Unique ID representing the customer.
  • For customers: Read only generated on creation.
  • For activities: Optional, links Activity with a single customer object.
Activity, Customer
dataElements Data fields defined by the fieldsets assigned to this object. Fields classified as PII or Non-PII Encrypted are automatically encrypted. User defined data. Activity, Customer, Request
fieldsets Fieldsets assigned to this object. You must assign at least one fieldset to an object. User assigned fieldsets. Activity, Customer, Request
lastUpdated Time stamp of when the object was last updated. lastUpdated value must match the database value for synchronization purposes. Read-only, automatically set when user updates a customer. Users cannot set this property. Activity, Customer, Request
mediaType User-defined media types. Possible values include:
  • CHAT
  • EMAIL
  • EVENT
  • MOBILE
  • SOCIAL
  • VOICE
  • WEB
Activity
podList List of activities associated with this request. User defined POD IDs. Request
requestID Unique ID representing the request.
  • For requests: Read-only, generated on creation.
  • For activities: Optional, links activity with a single request.
Activity, Request
state State of activity. Possible values include:
  • ACTIVE: Activity is instantiated and active.
  • CLOSED: Activity is closed by user or application.
Activity
tags List of tags for an activity. Optional, number and length cannot exceed 256kb. Activity

Create Objects

Use ContextServiceClient().create() to create a new object. create() automatically creates default object properties:

  • customer, pod, or request ID
  • created
  • lastUpdated
  • contributors (defaults to username associated with client connectionData unless overridden)

You must specify which fieldsets are used in any new object you create. create() returns a ClientResponse object. The Context Service SDK provides helper utilities that work with Context Service objects. For example, SDKUtils.getIdFromResponse(newlyCreatedPod) returns the ID of the new object.

These examples show how to create a new object. The functions take an initialized contextServiceClient as the input parameter and return the newly created object.

/**
 * Create POD with default fields and fieldsets.
 * @param contextServiceClient an initialized ContextServiceClient
 * @return a newly-created pod with the cisco.base.pod fieldset
 */
public static Pod createPodWithBaseFieldset(ContextServiceClient contextServiceClient) {
    Pod pod = new Pod(
            DataElementUtils.convertDataMapToSet(
                    new HashMap<String, Object>() {{
                        put("Context_Notes", "Notes about this context.");
                        put("Context_POD_Activity_Link", "http://myservice.example.com/service/ID/xxxx");
                    }}
            )
    );
    pod.setFieldsets(Arrays.asList("cisco.base.pod"));
    contextServiceClient.create(pod);
    return pod;
}

/**
 * Create a Customer with default fields and fieldsets.
 * @param contextServiceClient an initialized ContextServiceClient
 * @return a newly-created customer with the cisco.base.customer fieldset
 */
public static Customer createCustomerWithBaseFieldset(ContextServiceClient contextServiceClient) {
    Customer customer = new Customer(
            DataElementUtils.convertDataMapToSet(
                    new HashMap<String, Object>() {{
                        put("Context_Work_Email", "john.doe@example.com");
                        put("Context_Work_Phone", "555-555-5555");
                        put("Context_First_Name", "John");
                        put("Context_Last_Name", "Doe");
                        put("Context_Street_Address_1", "123 Sesame Street");
                        put("Context_City", "Detroit");
                        put("Context_State", "MI");
                        put("Context_Country", "US");
                        put("Context_ZIP", "90210");
                    }}
            )
    );
    customer.setFieldsets(Arrays.asList("cisco.base.customer"));
    contextServiceClient.create(customer);
    return customer;
}

/**
 * Create a Request with default fields and fieldsets.
 * @param contextServiceClient an initialized ContextServiceClient
 * @return a newly-created request with the cisco.base.request fieldset
 */
public static Request createRequestWithBaseFieldset(ContextServiceClient contextServiceClient) {
    Request request = new Request(
            DataElementUtils.convertDataMapToSet(
                    new HashMap<String, Object>() {{
                        put("Context_Description", "Request1 Description");
                        put("Context_Title", "Request1 Title");
                    }}
            )
    );
    request.setFieldsets(Arrays.asList("cisco.base.request"));
    contextServiceClient.create(request);
    return request;
}

Create Activities with Additional Parameters

Create Activities with Context Data

Activities not associated with a customer or request are deemed orphan. To add value to an activity, provide context data in the activity and associate it with a customer or request. Create a new activity and specify the DataElements in the constructor: Pod(Set<DataElement> dataElements). Individual data elements must map to a field that exists in the fieldset. You can add multiple fields at a time and define the fields in a Java map. Use the helper function DataElementUtils.convertDataMapToSet() to convert the Java map to a set, as required by the object constructor.

Associate Activities with a Customer

You can map activities to a single customer. By associating a customer with activities, you help map the customer journey and provide context to the customer's interactions.

The first example shows how to associate a single activity with a single customer. The second example shows how to associate activities with a single customer. The functions take an initialized contextServiceClient and a customer object, and return one or more activity objects.

/**
 * Create a POD and associate a customer to the POD.
 * @param contextServiceClient an initialized ContextServiceClient
 * @param customer a pre-existing Customer object
 * @return a POD associated with the Customer
 */
public static Pod createPodWithCustomer(ContextServiceClient contextServiceClient, Customer customer) {
    Pod pod = new Pod(
            DataElementUtils.convertDataMapToSet(
                    new HashMap<String, Object>() {{
                        put("Context_Notes", "Notes about this context.");
                        put("Context_POD_Activity_Link", "http://myservice.example.com/service/ID/xxxx");
                    }}
            )
    );
    pod.setFieldsets(Arrays.asList("cisco.base.pod"));
    pod.setCustomerId(customer.getId());
    contextServiceClient.create(pod);
    return pod;
}

/**
 * Create multiple PODS associated with one customer.
 * One Customer can have many Pods associated with it.
 * @param contextServiceClient an initialized ContextServiceClient
 * @param customer a pre-existing Customer object
 * @return a List of Pods associated with the same Customer
 */
public static List<Pod> createMultiplePodsWithSameCustomer(ContextServiceClient contextServiceClient, Customer customer) {
    List<Pod> pods = new ArrayList<>();
    for (int i = 0; i < 5; i++) {
        pods.add(createPodWithCustomer(contextServiceClient, customer));
    }
    return pods;
}

Associate Activities with a Request

You can map activities to a single request. Associating a request with one or more activities helps map the customer journey and provides context that helps serve customers better.

The first example shows how to associate a single activity with a single request. The second example shows how to associate multiple activities with a single request. The functions take an initialized contextServiceClient and a request object and return one or more activity objects.

/**
 * Create a POD with an associated request.
 * @param contextServiceClient an initialized ContextServiceClient
 * @param request a pre-existing Request object
 * @return a pod associated with the Request
 */
public static Pod createPodWithRequest(ContextServiceClient contextServiceClient, Request request) {
    Pod pod = new Pod(
            DataElementUtils.convertDataMapToSet(
                    new HashMap<String, Object>() {{
                        put("Context_Notes", "Notes about this context.");
                        put("Context_POD_Activity_Link", "http://myservice.example.com/service/ID/xxxx");
                    }}
            )
    );
    pod.setFieldsets(Arrays.asList("cisco.base.pod"));
    pod.setRequestId(request.getId());
    contextServiceClient.create(pod);
    return pod;
}

/**
 * Demonstrate the many-to-one relationship between Customers and Pods
 * One Customer can have many Pods associated with it.
 * @param contextServiceClient an initialized ContextServiceClient
 * @param request a pre-existing Request object
 * @return a List of Pods associated with the same Request
 */
public static List<Pod> createMultiplePodsWithSameRequest(ContextServiceClient contextServiceClient, Request request) {
    List<Pod> pods = new ArrayList<>();
    for (int i = 0; i < 5; i++) {
        pods.add(createPodWithRequest(contextServiceClient, request));
    }
    return pods;
}

Associate Activities with Customers and Requests

Activities can map to a single customer or a single request. Map the activity to the customer or request by using the appropriate set method:

  • setRequestId(UUID)
  • setCustomerId(UUID)

This example shows how to create an activity with associated customer and request data. The function uses as input parameters:

  • An initialized contextServiceClient
  • A created customer object
  • A created request object

and returns the newly created activity.

/**
 * Create a POD with an associated customer and an associated request.
 * @param contextServiceClient an initialized ContextServiceClient
 * @param customer pre-existing Customer object
 * @param request pre-existing Request object
 * @return a pod associated with both a customer and a request
 */
public static Pod createPodWithCustomerAndRequest(ContextServiceClient contextServiceClient, Customer customer, Request request) {
    Pod pod = new Pod(
            DataElementUtils.convertDataMapToSet(
                    new HashMap<String, Object>() {{
                        put("Context_Notes", "Notes about this context.");
                        put("Context_POD_Activity_Link", "http://myservice.example.com/service/ID/xxxx");
                    }}
            )
    );
    pod.setFieldsets(Arrays.asList("cisco.base.pod"));
    pod.setCustomerId(customer.getId());
    pod.setRequestId(request.getId());
    contextServiceClient.create(pod);
    return pod;
}

Get Activities Associated with Requests

searchForPodByRequestId returns a list of activities associated with the specified request ID.

/**
 * Search for Pod by RequestID.
 *
 * @param contextServiceClient  an initialized Context Service Client
 * @param id the Request ID
 * @return List of Pods associated with the Request ID
 */
public static List<Pod> searchForPodByRequestId (ContextServiceClient contextServiceClient, String id) {
    SearchParameters params = new SearchParameters();
    params.add("requestId", id);
    // Note that for a single parameter, it doesn't matter whether we use the AND or OR Operation.
    List<Pod> result = contextServiceClient.search(Pod.class, params, Operation.OR);

    for (Pod pod : result) {
        LOGGER.info("Found pod: " + pod.toString());
    }
    return result;
}

Add New Contributor to an Activity

A contributor is a machine or user that creates or modifies an activity. Contributor properties include:

  • Contributor type: Either MACHINE or USER
  • Username: Read-only value displaying the client ID associated with the connection data the application uses. You cannot override this setting.
  • Optional ID: Specify an optional contributor ID as a string.

By default, every time an activity is created or updated, a contributor containing the machine account name is added to the contributor list for the activity. setNewContributor() allows you to append a contributor ID (such as an agent ID) to an activity and specify the contributor type.

This example shows how to add a new contributor to an activity, using an initialized contextServiceClient and a podId as the input parameters. It returns the updated activity with the new contributor information.

/**
 * Add a new contributor to a POD.
 * @param contextServiceClient an initialized ContextServiceClient
 * @param podId
 * @return the updated POD with the new contributor
 */
public static Pod addContributorToPod(ContextServiceClient contextServiceClient, UUID podId) {
    Pod pod = contextServiceClient.get(Pod.class, podId.toString());
    Contributor contributor = new Contributor(ContributorType.USER, "AgentId");
    pod.setNewContributor(contributor);
    contextServiceClient.update(pod);
    return pod;
}

Update Objects

These examples show how to:

  1. Get the specified object using ContextServiceClient.get().
  2. Updated the returned object using ContextServiceClient.update().

You must first get the object to obtain the most recent lastUpdated field to pass back to update(). This check ensures you are updating the most recent version of the object. Your update fails if you attempt to update an object but do not pass back the most recent lastUpdated property.

The function takes an initialized contextServiceClient and the object ID as the input parameters and returns the updated object.

/**
 * Update a POD.
 * @param contextServiceClient an initialized ContextServiceClient
 * @param podId
 * @return the updated POD
 */
public static Pod updatePod(ContextServiceClient contextServiceClient, UUID podId) {
    Pod pod = contextServiceClient.get(Pod.class, podId.toString());
    // Add a media type
    pod.setMediaType(PodMediaType.SOCIAL);
    // update DataElements
    Map<String, Object> updateData =  DataElementUtils.convertDataSetToMap(pod.getDataElements());
    updateData.put("Context_Notes", "pod was modified");
    pod.setDataElements(DataElementUtils.convertDataMapToSet(updateData));
    // Send the update
    contextServiceClient.update(pod);
    return pod;
}

/**
 * Update a Customer.
 * @param contextServiceClient an initialized ContextServiceClient
 * @param customerId
 * @return the updated Customer
 */
public static Customer updateCustomer(ContextServiceClient contextServiceClient, UUID customerId) {
    Customer customer = contextServiceClient.get(Customer.class, customerId.toString());
    Map<String, Object> updateData = DataElementUtils.convertDataSetToMap(customer.getDataElements());
    updateData.put("Context_Street_Address_1", "333 Sesame Street");
    customer.setDataElements(DataElementUtils.convertDataMapToSet(updateData));
    contextServiceClient.update(customer);
    return customer;
}

/**
 * Update a Request.
 * @param contextServiceClient an initialized ContextServiceClient
 * @param requestId
 * @return the updated Request
 */
public static Request updateRequest(ContextServiceClient contextServiceClient, UUID requestId) {
    Request request = contextServiceClient.get(Request.class, requestId.toString());
    Map<String, Object> updateData = DataElementUtils.convertDataSetToMap(request.getDataElements());
    updateData.put("Context_Title", "Updated Context Title");
    request.setDataElements(DataElementUtils.convertDataMapToSet(updateData));
    contextServiceClient.update(request);
    return request;
}

Delete Objects

This example shows how to delete an object by passing the object URL to ContextServiceClient().delete().

/**
 * Delete a POD.
 * @param contextServiceClient an initialized ContextServiceClient
 * @param pod a pod object to delete
 */
public static void deletePod(ContextServiceClient contextServiceClient, Pod pod){
    contextServiceClient.delete(Pod.class, pod.getId().toString());
}

/**
 * Delete a Customer.
 * @param contextServiceClient an initialized ContextServiceClient
 * @param customer a customer object to delete
 */
public static void deleteCustomer(ContextServiceClient contextServiceClient, Customer customer){
    contextServiceClient.delete(Customer.class, customer.getId().toString());
}

/**
 * Delete a Request
 * @param contextServiceClient an initialized ContextServiceClient
 * @param request a request object to delete
 */
public static void deleteRequest(ContextServiceClient contextServiceClient, Request request){
    contextServiceClient.delete(Request.class, request.getId().toString());
}

Flush Object Data

In lab mode, use flush to purge all data before you set up your organization, or to clear data created during tests.

You can delete all data using flush() with the ContextServiceClient method of object class, contextServiceClient.flush(Pod.class);.

Flush is a non-blocking method and allows you to continue to perform other operations. This can potentially result in loss of data. Call waitForFlushComplete to wait for the flush function to complete before executing other commands.

This example shows how to flush all objects together. The example also executes a 30 second wait for each flush operation to complete, before throwing an exception.

/**
 * Flush all entities (pods, request, customers) at once and wait for each flush to complete.
 * Note: Flush is only supported in lab mode.
 *
 * @param contextServiceClient Context Service Client
 * @throws InterruptedException, when an interrupt signal is caught during wait
 * @throws TimeoutException, when flush is not complete and the timeout expires
 */
public static void flushAllEntities(ContextServiceClient contextServiceClient) throws InterruptedException, TimeoutException {
    LOGGER.info("Flushing workgroup data...");

    contextServiceClient.flush(Pod.class);
    contextServiceClient.flush(Request.class);
    contextServiceClient.flush(Customer.class);

    FlushStatusBean status = null;

    // Use SDK to wait for flush to complete.  In this case, allow up to 30 seconds...
    status = contextServiceClient.waitForFlushComplete(Pod.class, MAX_FLUSH_WAIT_IN_SECONDS);
    if (status.isCompleted()) {
        LOGGER.info("Flush of pods complete. Flushed " + status.getNumberFlushed() + " pods.");
    } else {
        LOGGER.info("Flush of pods not complete. Flushed " + status.getNumberFlushed() + " pods. " + (!StringUtils.isEmpty(status.getMessage()) ? status.getMessage() : ""));
        throw new TimeoutException();
    }

    status = contextServiceClient.waitForFlushComplete(Request.class, MAX_FLUSH_WAIT_IN_SECONDS);
    if (status.isCompleted()) {
        LOGGER.info("Flush of requests complete. Flushed " + status.getNumberFlushed() + " requests.");
    } else {
        LOGGER.info("Flush of requests not complete. Flushed " + status.getNumberFlushed() + " requests. " + (!StringUtils.isEmpty(status.getMessage()) ? status.getMessage() : ""));
        throw new TimeoutException();
    }

    status = contextServiceClient.waitForFlushComplete(Customer.class, MAX_FLUSH_WAIT_IN_SECONDS);
    if (status.isCompleted()) {
        LOGGER.info("Flush of customers complete. Flushed " + status.getNumberFlushed() + " customers.");
    } else {
        LOGGER.info("Flush of customers not complete. Flushed " + status.getNumberFlushed() + " customers. " + (!StringUtils.isEmpty(status.getMessage()) ? status.getMessage() : ""));
        throw new TimeoutException();
    }

    LOGGER.info("Flushed workgroup data.");

}

Search for Objects (lookup)

Use contextServiceClient().search() to lookup objects. Search typically uses a list of key/value pairs where the key corresponds to a field name. Search is an exact word match and is case-insensitive in English.

By default, all fields are searchable. To make a field non-searchable, set the searchable field property to false.

Use an AND or OR modifier to combine multiple search terms.

search() returns a list of objects that match your search criteria. All results will be case-insensitive exact matches. White space is trimmed from the ends of any search.

Search for Customers Matching Multiple Fields

This example shows a basic customer search, using both first and last name fields. The search returns customer records that match Jane in the first name field and Doe in the last name field. The function takes an initialized Context Service client as the input parameter.

/**
 * Search for customer matching all specified fields.
 * 
 * @param contextServiceClient an initialized Context Service Client
 * @return List of customers matching the criteria
 */
public static List<Customer> searchForCustomerByFirstAndLastName (ContextServiceClient contextServiceClient) {
    SearchParameters params = new SearchParameters();
    params.add("Context_First_Name", "Jane");
    params.add("Context_Last_Name", "Doe");
    List<Customer> result = contextServiceClient.search(Customer.class, params, Operation.AND);

    for (Customer customer : result) {
        LOGGER.info("Found customer: " + customer.toString());
    }
    return result;
}

Special Search Keys

You can use special search keys to search for object metadata instead of the normal key/value lookups on fields. You can search based on one or a combination of any of the following special keys in addition to field keys:

  • id—Object ID.
  • customerId—Customer ID. Only valid when searching activities.
  • requestId—Request ID. Only valid when searching activities.
  • mediaType—POD Media Type
  • state—Activity state. Only valid when searching activities.
  • contributors.username—Username of any contributor or agent associated with an activity.
  • contributors.id—ID of any contributor or agent associated with an activity.
  • newContributor.username—Username of the last contributor who updated the activity.
  • newContributor.id—ID of the last contributor who updated the activity.
  • tags—Activities containing a specified tag.
  • maxEntries—Maximum number of results to return. The default value is 50, with a minimum of value 1 and maximum value of 200.
  • startIndex—Specifies where in the index to start searching from. Search results are returned beginning from the startIndex value. The default startIndex value is 0. Only valid when searching fields and fieldsets.
  • startDateEarliest possible time returned for lastUpdated timestamp. The default startDate time is the last 24 hours. Entries are returned from startDate to "now".
  • endDateLatest possible time returned for lastUpdated timestamp. Search for objects updated between startDate and endLastUpdateDate. If used alone, returns objects updated before current date.
  • startCreatedDate—Search for objects created starting with the specified date. All entries created from the specified start date are returned.
  • endCreatedDate—Search for objects created ending with the specified date. All entries created up to the specified end date are returned.
  • summary—Specify if search summary is returned. This is a boolean value and defaults to false. Set it to true to return a list of objects containing only the object IDs. Number of matches is limited to a maximum of 100,000 entries.

Use the RFC339Date class to format dates.

Searching for objects at the time they were created may not always work as expected. search() can take up to a minute to return results. If you need to look up information immediately after object creation, use get() instead.

Search for Activity Using Created Date

This example shows how to search for an activity by activity created date. The input takes a range of dates formatted using the RFC339Date class.

/**
 * Search for PODs created within a specified date and time range.
 * 
 * @param contextServiceClient  an initialized Context Service Client
 * @param startTime return PODs that were created no earlier than this time
 * @param endTime return PODs that were created no later than this time
 * @return List of PODs that were created within the date/time range.
 */
public static List<Pod> searchForPodsByCreateDateRange (ContextServiceClient contextServiceClient, long startTime, long endTime) {
        // Convert times (msec) to Date/Time strings...
        String startDate = new RFC3339Date(startTime).toString();
        String endDate = new RFC3339Date(endTime).toString();

        SearchParameters params = new SearchParameters();
        params.add("startCreatedDate", startDate);
        params.add("endCreatedDate", endDate);
        List<Pod> result = contextServiceClient.search(Pod.class, params, Operation.AND);

        for (Pod pod : result) {
            LOGGER.info("Found pod: " + pod.toString());
        }
        return result;
    }

Search for Customers Based on a Field

This example shows how to search for customers by first name or last name.

/**
 * Search for customer matching any of the specified fields.
 *
 * @param contextServiceClient  an initialized Context Service Client
 * @return List of customers matching the criteria
 */
public static List<Customer> searchForCustomerByFirstOrLastName (ContextServiceClient contextServiceClient) {
    SearchParameters params = new SearchParameters();
    params.add("Context_First_Name", "Jane");
    params.add("Context_Last_Name", "Doe");
    List<Customer> result = contextServiceClient.search(Customer.class, params, Operation.OR);

    for (Customer customer : result) {
        LOGGER.info("Found customer: " + customer.toString());
    }
    return result;
}

Search for Activity Using ID

These examples shows how to search for an activity using customer ID and POD ID.

/**
 * Search for Pod by CustomerID.
 * 
 * @param contextServiceClient  an initialized Context Service Client
 * @param id the Customer ID
 * @return List of Pods associated with the Customer ID
 */
public static List<Pod> searchForPodByCustomerId (ContextServiceClient contextServiceClient, String id) {
    SearchParameters params = new SearchParameters();
    params.add("customerId", id);
    // Note that for a single parameter, it doesn't matter whether we use the AND or OR Operation.
    List<Pod> result = contextServiceClient.search(Pod.class, params, Operation.OR);

    for (Pod pod : result) {
        LOGGER.info("Found pod: " + pod.toString());
    }
    return result;
}
/**
 * Search for Pod by List of Pod IDs.
 * 
 * @param contextServiceClient  an initialized Context Service Client
 * @param idList the list of Pod IDs
 * @return List of Pods matching any of the IDs in the list
 */
public static List<Pod> searchForPodByListOfIds (ContextServiceClient contextServiceClient, List<String> idList) {
    SearchParameters params = new SearchParameters();
    params.addAll("id", idList);

    // For a list, make sure to use OR; since no Pod can match ALL of different IDs.
    List<Pod> result = contextServiceClient.search(Pod.class, params, Operation.OR);

    for (Pod pod : result) {
        LOGGER.info("Found pod: " + pod.toString());
    }
    return result;
}

Search for Activity Using Last Contributor

This example shows how to search for an activity last updated or modified by the specified contributor.

/**
 * Search for PODs based on the last contributor, or the last person  to create or modify the POD.
 * 
 * @param contextServiceClient  an initialized ContextServiceClient
 * @param contributorUsername the username of a contributor
 * @return a list of PODs last modified by the given contributor
 */
public static List<Pod> searchForPodsByLastContributor(ContextServiceClient contextServiceClient, final String contributorUsername) {
    SearchParameters params = new SearchParameters(){{
        add("newContributor.username", contributorUsername);
    }};
    List<Pod> result = contextServiceClient.search(Pod.class, params, Operation.AND);

    for (Pod pod: result) {
        LOGGER.info("Found pod: " + pod.toString());
    }
    return result;
}

Search for Activity Using Contributor

This example shows how to search for an activity that was created or modified, at any time, by the specified contributor.

/**
 * Search for all PODs modified by the specified contributor.
 * 
 * @param contextServiceClient  an initialized ContextServiceClient
 * @param contributorUsername the username of a contributor
 * @return a list of PODs modified by the given contributor
 */
public static List<Pod> searchForPodsByContributor(ContextServiceClient contextServiceClient, final String contributorUsername) {
    SearchParameters params = new SearchParameters(){{
        add("contributors.username", contributorUsername);
    }};
    List<Pod> result = contextServiceClient.search(Pod.class, params, Operation.AND);

    for (Pod pod: result) {
        LOGGER.info("Found pod: " + pod.toString());
    }
    return result;
}

Search for Activity Using Tag

This example shows how to search for an activity that contains the tag sales or marketing.

/**
 * Returns list of PODs that any of the specified tags.  
 *
 * @param contextServiceClient  an initialized Context Service Client
 * @return List of Pods that match at least one of the tags
 */
public static List<Pod> searchForPodsTaggedAsSalesOrMarketing (ContextServiceClient contextServiceClient) {
    SearchParameters params = new SearchParameters();
    params.add("tags", "sales");
    params.add("tags", "marketing");
    List<Pod> result = contextServiceClient.search(Pod.class, params, Operation.OR);

    for (Pod pod : result) {
        LOGGER.info("Found pod: " + pod.toString());
    }
    return result;
}

You can also search for any activities that contain all of the specified tags. This example shows how to search for an activity with the tags major, issue, and preferred-customer.

/**
 * Returns list of PODs that match ALL specified tags.
 *
 * @param contextServiceClient  an initialized Context Service Client
 * @return List of Pods that match all of the tags
 */
public static List<Pod> searchForPodsTaggedAsMajorIssueForPreferredCustomer (ContextServiceClient contextServiceClient) {
    SearchParameters params = new SearchParameters();
    params.add("tags", "issue");
    params.add("tags", "major");
    params.add("tags", "preferred-customer");
    List<Pod> result = contextServiceClient.search(Pod.class, params, Operation.AND);

    for (Pod pod : result) {
        LOGGER.info("Found pod: " + pod.toString());
    }
    return result;
}

Search for Activity Using Query String

If you do not know exact field names, use the query_string parameter to search for activity, customer, and request objects. query_string must be a space-delimited string. All values in query_string are combined with the OR operator, regardless of the search operator specified in the function. You can use query_string only with customerId. All other search parameters are ignored.

To search for a phrase that contains a whitespace, wrap the phrase in quotes “”. For example, “123 Main St”.

This example shows how search for an activity with this query string:

Ivanna.Buy@prospect.com 222-222-2222 banana \"Ivanna Buy\".

/**
 *  Search for PODs with a specified query_string parameter.
 * 
 *  @param contextServiceClient  an initialized ContextServiceClient
 *  @return a list of PODs found by the query_string sub queries
 */
public static List<Pod> searchForPodsByQueryString(ContextServiceClient contextServiceClient){
    SearchParameters params = new SearchParameters();
    params.add("query_string", "Ivanna.Buy@prospect.com 222-222-2222 banana \"Ivanna Buy\"");

    List<Pod> result = contextServiceClient.search(Pod.class, params, Operation.AND);

    for (Pod pod: result){
        LOGGER.info("Found pod: " + pod.toString());
    }
    return result;
}

Complex Query Examples

You can combine multiple search parameters to build a complex query string. This example shows how to search for activities with a specified custom field, time range, and tag.

/**
 * Build a complex query checking for a custom field, a time range, and a tag.
 * 
 * @param contextServiceClient  an initialized Context Service Client
 * @param startTime return PODs that were created no earlier than this time
 * @param endTime return PODs that were created no later than this time
 * @return a list of Pods matching the query
 */
public static List<Pod> searchForPodsByCustomFieldAndDateRangeAndTag(ContextServiceClient contextServiceClient, String customField, long startTime, long endTime) {
    // Convert times (msec) to Date/Time strings...
    SimpleDateFormat sdf = new SimpleDateFormat(DATE_STRING_FORMAT);
    sdf.setTimeZone(TimeZone.getTimeZone("GMT"));
    String startDate = sdf.format(new Date(startTime));
    String endDate = sdf.format(new Date(endTime));

    SearchParameters params = new SearchParameters();
    params.add("startCreatedDate", startDate);
    params.add("endCreatedDate", endDate);
    params.add("sdkExample_fieldOne", customField);
    params.add("tags", "cancellation");
    List<Pod> result = contextServiceClient.search(Pod.class, params, Operation.AND);

    for (Pod pod : result) {
        LOGGER.info("Found pod: " + pod.toString());
    }
    return result;
}

Search for Activity Using State

You can search by the state of an activity. This example shows how to search for all active activities.

/**
 * Search for all active PODs.
 * 
 * @param contextServiceClient  an initialized ContextServiceClient
 * @return a list of open PODs
 */
public static List<Pod> searchForActivePods(ContextServiceClient contextServiceClient) {
    SearchParameters params = new SearchParameters(){{
        add("state", PodState.ACTIVE);
    }};
    List<Pod> result = contextServiceClient.search(Pod.class, params, Operation.AND);

    for (Pod pod : result) {
        LOGGER.info("Found pod: " + pod.toString());
    }
    return result;
}

Fields and Fieldsets

Context Service contains the following default or base fields and fieldsets:

  • cisco.base.pod for use with activities
  • cisco.base.customer for use with customers
  • cisco.base.request for use with requests

You can use fieldsets with any activity, customer, or request object.

By default, all fields are set to be searchable. Change the value of the searchable property to specify if a field is searchable or non-searchable. Valid values are:

  • No value—Default value of a field. Indicates that the field is searchable
  • false—specifies that a field is non-searchable.
  • true—specifies that a field is searchable

Default or Base Fields and Fieldsets

Fields and fieldsets available out-of-the-box currently include:

Fieldset cisco.base.request

field classification dataType
Context_Description ENCRYPTED string
Context_Title ENCRYPTED string

Fieldset cisco.base.pod

field classification dataType
Context_Notes ENCRYPTED string
Context_POD_Activity_Link UNENCRYPTED string
Context_POD_Source_Cust_Name PII string
Context_POD_Source_Email PII string
Context_POD_Source_Phone PII string

Fieldset cisco.base.customer

field classification dataType
Context_Customer_External_ID PII string
Context_City PII string
Context_Country PII string
Context_First_Name PII string
Context_Home_Email PII string
Context_Home_Phone PII string
Context_Last_Name PII string
Context_Mobile_Phone PII string
Context_State PII string
Context_Street_Address_1 PII string
Context_Street_Address_2 PII string
Context_Work_Email PII string
Context_Work_Phone PII string
Context_ZIP PII string
Context_Preferred_Language UNENCRYPTED string

Values for the Context_Preferred_Language field must be a supported language code. For a list of supported language codes, see Context Service SDK Supported Language Codes. Entering an unsupported value for the Context_Preferred_Language field will throw an error.

Custom Fields and Fieldsets

In addition to the base fields and fieldsets built into Context Service, you can create custom fields and fieldsets specific to your data model. You can create up to 100 custom fields and 1,000 custom fieldsets for your organization.

To define new fields, specify:

  • field name
  • classification
  • datatype
  • searchable
  • translations

After you have defined your fields, create one or more fieldsets to group your fields into sets. A field can exist in multiple fieldsets. This allows you to define a flexible data model.

Create New Fields

Use the field() constructor to create a new field, with these arguments:

  • id—ID for the field specified as a string. Field ID must be unique with a maximum of 475 characters.
  • classification—Security classification specified as a property of ElementClassification. Fields are classified as:
    • Unencrypted—Data is not encrypted and contains no personally identifiable information.
    • PII—Data is encrypted and contains personally identifiable information (PII).
    • Encrypted—Data is encrypted and contains no personally identifiable information.
  • dataType—Data type of the field specified as a property of ElementDataType. Valid data types are:
    • String
    • Double
    • Boolean
    • Integer (32-bit)

The default value of a Boolean data type is false.

  • searchable—Specifies if the field is searchable. Set this value to true to allow search, and to false to make the field non-searchable. Fields are searchable by default if you do not set any value.
  • translations—Specifies if the field is translated. Valid values are null for no translations or a Map of ISO LanguageType Codes and the translated texts that define the field name.

This example shows how to create a field where the value is specified in English and translated in French. The function takes an initialized contextServiceClient as the input parameter and returns the new field.

/**
 * Create a field with translations.
 *
 * @param contextServiceClient an initialized Context Service Client
 * @return a field with translations
 */
public static Field createFieldWithTranslations(ContextServiceClient contextServiceClient){

    Map<String, Object> translations = new HashMap<>();
    translations.put(LanguageType.EN_US, "First Name");
    translations.put(LanguageType.FR, "Prenom");

    List<String> locales = new ArrayList<>();
    locales.add("en_US");
    locales.add("en_GB");
    locales.add("zh_CN");

    Field field = new Field("sdkExample_fieldOne", ElementClassification.PII, ElementDataType.STRING, false, translations, "true", locales);

    contextServiceClient.create(field);
    LOGGER.info("Created field: "+field.getId()+" with translations: "+translations.toString() + " and locales: " + String.join(", ", locales));

    return field;
}

Create New Fieldsets

Use fieldset() to add one or more fields to a fieldset, with these arguments:

  • ID—ID for the fieldset specified as a string. Fieldset ID must be unique with a maximum of 475 characters.
  • Fields—Set of strings containing the fields to include in this fieldset. Fields must be defined before you can add them to a fieldset.

This example shows how to create two new fields and add them to a fieldset. The function takes an initialized contextServiceClient as the input parameter and returns the new fieldset with two new fields.

/**
 * Create two new fields and add them to a fieldset.
 *
 * @param contextServiceClient initialized Context Service Client
 * @return a fieldset
 */
public static FieldSet createFieldSet(ContextServiceClient contextServiceClient){

    Field field1 = new Field("sdkExample_fieldOne", ElementClassification.UNENCRYPTED, ElementDataType.STRING, false, null);
    contextServiceClient.create(field1);

    Field field2 = new Field("sdkExample_fieldTwo", ElementClassification.UNENCRYPTED, ElementDataType.STRING, false, null);
    contextServiceClient.create(field2);

    FieldSet fieldset = new FieldSet("sdkExample_fieldSet", new HashSet<>(Arrays.asList(field1.getIdentifier(), field2.getIdentifier())), false);
    contextServiceClient.create(fieldset);

    LOGGER.info("Created fieldset: "+fieldset.getId()+" with fields: "+fieldset.getFields().toString());
    return fieldset;
}

Update Fieldsets

You can update fieldsets to add new fields, remove fields, or modify fields.

This example shows how to add a new field to a fieldset. The function takes an initialized contextServiceClient and the chosen fieldset as input parameters and returns the updated field.

/**
 * Add a new field to the specified fieldset.
 *
 * @param contextServiceClient initialized Context Service Client
 * @param fieldSet a fieldset to be updated
 * @return a fieldset
 */
public static FieldSet updateFieldSet(ContextServiceClient contextServiceClient, FieldSet fieldSet){

    Field field = new Field("sdkExample_fieldThree", ElementClassification.UNENCRYPTED, ElementDataType.STRING, false, null);

    Set<String> fields = fieldSet.getFields();
    fields.add(field.getIdentifier());
    fieldSet.setFields(fields);

    LOGGER.info("Updated fieldSet: "+fieldSet.getId()+" by adding field "+field.getId());
    return fieldSet;
}

Delete Fieldsets

Deleting a fieldset does not delete the fields included in that fieldset.

This example shows how to delete a fieldset. The function takes an initialized contextServiceClient and the name of the fieldset to be deleted as input arguments.

/**
 * Delete the specified fieldset.
 *
 * @param contextServiceClient Context Service Client
 * @param fieldSet a fieldset
 */
public static void deleteFieldSet(ContextServiceClient contextServiceClient, FieldSet fieldSet){
    contextServiceClient.delete(fieldSet);
    LOGGER.info("Deleted fieldSet: "+fieldSet.getId());
}

Search Fields and Fieldsets

Use a search query to search fields and fieldsets.

These examples show how to search using a search query specifying ID and field or fieldset name. The functions take an initialized contextServiceClient and the search query as the input argument and return a list of fields or fieldsets that match the search query.

/**
 * Search fieldsets using a search query.
 *
 * @param contextServiceClient an initialized Context Service Client
 * @return List<FieldSet> a list of fieldsets that match the search query
 */
public static List<FieldSet> searchFieldSet(ContextServiceClient contextServiceClient){

    LOGGER.info("Constructing search query ...");
    SearchParameters params =  new SearchParameters();
    params.add("id","sdkExample_fieldSet");

    LOGGER.info("Searching for fieldSet in ContextService based on query: " + params.toString());
    List<FieldSet> fieldsets = contextServiceClient.search(FieldSet.class, params, OR);
    for (FieldSet fieldset : fieldsets) {
        LOGGER.info("Found Field Set " + fieldset.getId());
    }
    return fieldsets;
}

/**
 * Search fieldsets using a search query.
 *
 * @param contextServiceClient an initialized Context Service Client
 * @return List<FieldSet> a list of fieldsets that match the search query
 */
public static List<FieldSet> searchFieldSet(ContextServiceClient contextServiceClient){

    LOGGER.info("Constructing search query ...");
    SearchParameters params =  new SearchParameters();
    params.add("id","sdkExample_fieldSet");

    LOGGER.info("Searching for fieldSet in ContextService based on query: " + params.toString());
    List<FieldSet> fieldsets = contextServiceClient.search(FieldSet.class, params, OR);
    for (FieldSet fieldset : fieldsets) {
        LOGGER.info("Found Field Set " + fieldset.getId());
    }
    return fieldsets;
}

View Usage Metrics for Your Organization

The Context Service SDK provides methods to look at usage data for your organization, by using the Java Management Extensions (JMX) on your Context Service client connector.

Context Services returns various requested statistics by using methods with a specific parameter type and specific operation. For example, to get latency statistics for POD requests created, you specify Pod.Create as your parameter.

The parameters for countValue and statsValue are combinations of entities and operations.

Entities include:

  • Pod
  • Request
  • Customer
  • Field
  • FieldSet

Operations include:

  • Create
  • Get
  • Delete
  • Update
  • Search

For example, Pod.Create, Pod.Get, Pod.Delete, Pod.Update, Pod.Search are combinations of entities and operations. You can also get metrics for your application using Application.DiscoveryGetList.

You can get metrics for external types and operations. For example:

  • KmsRequest.create_EPHEMERAL_KEY_COLLECTION
  • KmsRequest.retrieve_Key
  • AccessTokenResponse.AccessToken
  • AccessTokenResponse.RefreshToken

You can also get the number of occurrences of an error by passing the parameter type with the error name, using the Get operator. For example, to see all POD not found errors, specify Pod.Get.Error.notFound.

Methods for Usage Metrics

Before using metrics on your data, you must connect to your platform Mbean server and get the context server Mbean with ObjectName com.cisco.context:type=ContextServiceClient. This example shows how to connect to the platform Mbean server:

/**
 * Create ContextServiceClient object
 */
public static void registerMbean() throws MBeanException, InstanceNotFoundException, ReflectionException {
    mbeanServer = ManagementFactory.getPlatformMBeanServer();
    try {
         contextObjName = new ObjectName(CONTEXT_OBJECT_NAME);
    } catch (MalformedObjectNameException e) {
        e.printStackTrace();
    }
}

countValue

Use countValue to get the value of a specific counter since statistics for this client was last reset.

Parameters

  1. params: a combination of the entity and an operation specified as a string. For example, Pod.Create.
  2. signature: allocates an instance of the class java.lang.String.

The method returns the number of the counter of specified type and operation combination.

Example Usage

Object result = Server.invoke(name, "countValue", countParams, signature);

statsSummary

Use statsSummary to get a full statistics summary as a JSON string of key-value pairs that are easily converted to a map. In addition to returning all metrics for your organization, statsSummary also returns:

  • startCollectionDateTime: the timestamp for when the data was flushed
  • statsRequestTime: the timestamp for when the method was invoked.

You can use this information to calculate the period of statistics collection.

Parameters

statsSummary does not require any additional parameters.

Example Usage

Object objSummary = Server.invoke(name, "statsSummary", null, null );

statsValue

Use statsValue to get latency statistics for a particular operation.

Parameters

  1. params: a combination of a type, an operation, and the statistics to measure specified as a string. For example, Pod.Create, min. Valid measurement statistics include avg, min, max, count.

  2. signature: allocates an instance of the class java.lang.String, java.lang.String.

The method returns a double which is the average, minimum, or maximum latency of the specified type and operation.

Example Usage

Object objSummary = Server.invoke(name, "statsValue", params, signature );

resetStats

Use resetStats to reset all statistics. By default all statistics are reset every 30 minutes. Call resetStats to clear all data collected and begin collecting again. Calling resetStats returns the timestamp indicating the reset time.

Parameters

resetStats does not require any parameters.

Example Usage

server.invoke(name, "resetStats", null, null );

absoluteCountValue

Use absoluteCountValue to get the value of a specific counter from the time the client is initialized.

Parameters

  1. params: a combination of the entity and an operation specified as a string. For example, Pod.Create.
  2. signature: allocates an instance of the class java.lang.String.

The method returns the number of the counter of specified type.

Example Usage

Object result = Server.invoke(name, "absoluteCountValue", params, signature);

absoluteStatsSummary

Use absoluteStatsSummary to get a full statistics summary as a JSON string of key-value pairs that are easily converted to a map. In addition to returning all metrics for your organization, absoluteStatsSummary also returns:

  • startDateTime: the timestamp for when the client started
  • statsRequestTime: the timestamp for when method was invoked

You can use this information to calculate the period of statistics collection.

Parameters

absoluteStatsSummary does not require any additional parameters

Example Usage

Object objSummary = Server.invoke(name, "absoluteStatsSummary", null, null );

absoluteStatsValue

Use absoluteStatsValue to get latency statistics for a particular operation from the time the client was initialized.

Parameters

  1. params: a combination of a type, an operation, and the measurement statistics specified as a string. For example, Pod.Create, min. Valid measurement statistics include avg, min, max, count.

  2. signature: allocates an instance of the class java.lang.String, java.lang.String.

Example Usage

Object objSummary = Server.invoke(name, "absoluteStatsValue", params, signature );

Get Minimum Latency

This example shows how to get the minimum latency of PODs deleted since the counter was last reset. The method takes Pod.Create parameter and min measurement statistics and returns a double indicating the latency.

/**
 * Get minimum latency of PODs deleted from the time the stats was last reset
 * @return Double - It returns the minimum latency of PODs deleted
 */
public static <T> Double invokeStatsValueMethod() throws MBeanException, InstanceNotFoundException, ReflectionException {
    final String statsOpName= "statsValue";
    final Object[] statsParams= {"Pod.Delete", "min"};
    final String[] statsValueSignature= new String[]{"java.lang.String", "java.lang.String"};
    Object result = mbeanServer.invoke(contextObjName, statsOpName, statsParams, statsValueSignature);
    return (Double) result.getClass().cast(result);
}

Get Specific Count

This example shows how to get the count of PODs created since the counter was last reset.

/**
 * Get the number of PODs created from the time the counter was last reset
 * @return Long - It returns the count of PODs created
 */
public static <T> Long invokeCountValueMethod() throws MBeanException, InstanceNotFoundException, ReflectionException {
    final String countOpName= "countValue";
    final String[] countValueSignature= new String[]{"java.lang.String"};
    final Object[] countParams= {"Pod.Create"};
    Object result =  mbeanServer.invoke(contextObjName, countOpName, countParams, countValueSignature);
    return (Long) result.getClass().cast(result);
}

Get Error Count

This example shows how to get the number of times a notFound error was thrown while getting a deleted POD count. The method takes Pod.Delete.error.errorName parameter, where errorName is notFound.

/**
 * Get the number of notFound errors thrown when trying to get a Pod since the counter was last reset
 * @return Long -It returns the count of notFound PODs
 */
public static <T> Long invokeCountErrorValueMethod() throws MBeanException, InstanceNotFoundException, ReflectionException {

    final String countOpName= "countValue";
    final String[] countValueSignature= new String[]{"java.lang.String"};
    final Object[] errorCountParams= {"Pod.Get.error.notFound"};
    Object result  =  mbeanServer.invoke(contextObjName, countOpName, errorCountParams, countValueSignature);
    return (Long) result.getClass().cast(result);
}

Get Counter Summary

This example shows how to get a summary of all statistics measured since the counter was last reset. This method does not take any input parameters. It returns a JSON String that contains all the stats and counts including the timestamp when the collection started and the timestamp when the request was made.

/**
 * Get summary of al statistics measured from the time the stats/counter was reset
 * @return String - It returns a JSON String that contains all the stats and counters including the timestamp when the collection started and the timestamp when the request was made
 */
public static <T> String invokeStatsSummaryMethod() throws MBeanException, InstanceNotFoundException, ReflectionException {
    final String summaryOpName= "statsSummary";
    Object result =  mbeanServer.invoke(contextObjName, summaryOpName, null, null);
    return (String) result.getClass().cast(result);
}

Reset all Statistics for Counter

This example shows how to reset all statistics and counts. By default all statistics are rest every 30 minutes. Call this method to clear all data collected and begin collecting again.

/**
 * Reset all statistics and counters
 */
public static void invokeResetMethod() throws MBeanException, InstanceNotFoundException, ReflectionException {
    final String resetOpName= "resetStats";
    mbeanServer.invoke(contextObjName, resetOpName, null, null);
}

Get Absolute Minimum Latency

This example shows how to get minimum latency for all PODs deleted since the client was initialized. The method takes Pod.Delete parameter and min measurement statistics and returns a double indicating the latency.

/**
 * Get minimum latency all PODs deleted from the time the client was initialized.
 * @return Double - It returns the minimum latency of PODSs deleted
 */
public static <T> Double invokeAbsoluteStatsValueMethod() throws MBeanException, InstanceNotFoundException, ReflectionException {
    final String statsOpName= "absoluteStatsValue";
    final Object[] statsParams= {"Pod.Delete", "min"};
    final String[] statsValueSignature= new String[]{"java.lang.String", "java.lang.String"};
    Object result = mbeanServer.invoke(contextObjName, statsOpName, statsParams, statsValueSignature);
    return (Double) result.getClass().cast(result);
}

Get Absolute Count

This example shows how to get the count of all PODs created since the client was initialized.

/**
 * Get the count of all POSs created from the time the client was initialized.
 * @return Long - It returns the number of PODs created.
 */
public static <T> Long invokeAbsoluteCountValueMethod() throws MBeanException, InstanceNotFoundException, ReflectionException {
    final String countOpName= "absoluteCountValue";
    final String[] countValueSignature= new String[]{"java.lang.String"};
    final Object[] countParams= {"Pod.Create"};
    Object result =  mbeanServer.invoke(contextObjName, countOpName, countParams, countValueSignature);
    return (Long) result.getClass().cast(result);
}

Get Absolute Summary

This example shows how to get a summary of all statistics measured since the client was initialized. This method does not take any input parameters. It returns a JSON string that contains all stats and counts, including:

  • The timestamp when the collection started.
  • The timestamp when the request was made.
/**
 * Get a summary of all statistics measured since client was initialized
 * @return String - It returns a JSON String that contains all the stats and counters including the timestamp when the client was initialized and the timestamp when the request was made.
 */
public static <T> String invokeAbsoluteStatsSummaryMethod() throws MBeanException, InstanceNotFoundException, ReflectionException {
    final String summaryOpName= "absoluteStatsSummary";
    Object result = mbeanServer.invoke(contextObjName, summaryOpName, null, null);
    return (String) result.getClass().cast(result);
}

Clean Up

When you are finished viewing usage metrics, unregister Mbean to clear all data.

/**
 * unregister Mbean from MBServer
 */
public static void unregisterMbean() {
    try {
        ObjectName contextObjName = new ObjectName(CONTEXT_OBJECT_NAME);
        if (mbeanServer.isRegistered(contextObjName))
            mbeanServer.unregisterMBean(contextObjName);
    } catch (JMException ex) {
        throw new IllegalStateException(ex);
    }
}

End-to-End Example

To download a code example that shows the general workflow of the Context Service SDK, see the public Context Services GitHub.

Refer to readme.md for more instructions on how to run the sample code.

Before running the example code:

  • Register your application with Context Service
  • Register all clients
/**
 * Show full initialization flow for Context Service,
 * demonstrate a basic operation.
 */
public static void main(String ... args) {
    // Load our pre-created connection data
    String connectionData = ConnectionData.getConnectionData();

    // Initialize connector factory
    String pathToConnectorProperty = Paths.get("./connector.property").toAbsolutePath().toString();
    ConnectorFactory.initializeFactory(pathToConnectorProperty);
    LOGGER.info("Initialized Connector Factory");

    // Create Management connector instance
    ManagementConnector managementConnector = ConnectorFactory.getConnector(ManagementConnector.class);
    // Create Context Service Client instance
    ContextServiceClient contextServiceClient = ConnectorFactory.getConnector(ContextServiceClient.class);

    String hostname = "doctest.example.com";
    ConnectorInfoImpl connInfo = new ConnectorInfoImpl(hostname);
    ConnectorConfiguration configuration = new ConnectorConfiguration(){{
        addProperty("LAB_MODE", true); // exclude this line for production mode
        addProperty("REQUEST_TIMEOUT", 10000);

    }};

    // Add Management connector state listener. It needs to be done before calling init on the connector
    CustomConnectorStateListener mgmtConnectorStateListener = addStateListenerToMgmtConnector(managementConnector);
    // Add CredentialsChangedListener to Management connector. It needs to be added before calling init on the connector
    CredentialsChangedListener credentialsChangedListener = addCustomCredentialsListenerToManagementConnector(managementConnector,contextServiceClient);
    // Init Management connector
    managementConnector.init(connectionData, connInfo, configuration);
    // Now we can use the state listener to determine all the connector state changes
    try {
        waitForConnectorRegistered(mgmtConnectorStateListener, 3);
        LOGGER.info("Initialized Management connector");
    }catch(Exception e){
        LOGGER.error("Failed or timed out to initialize Management connector", e);
    }

    // Add Context Service Client connector state listener. It needs to be done before calling init on a connector
    CustomConnectorStateListener csConnectorStateListener = addStateListenerToContextConnector(contextServiceClient);

    // Init Context Service Client, reuse configuration we used for Management connector
    contextServiceClient.init(connectionData, connInfo, configuration);
    // Now we can use the state listener to determine all connector state changes
    try{
        waitForConnectorRegistered(csConnectorStateListener, 3);
        LOGGER.info("Initialized Context Service client");
    }catch(Exception e){
        LOGGER.error("Failed or timed out to initialize CS connector", e);
    }

    // Now we can use Context Service!
    // e.g. create a Pod:
    Pod pod = new Pod(
            DataElementUtils.convertDataMapToSet(
                    new HashMap<String, Object>() {{
                        put("Context_Notes", "Context Service Demo POD");
                        put("Context_POD_Activity_Link", "http://myservice.example.com/service/ID/xxxx");
                    }}
            )
    );
    pod.setFieldsets(Arrays.asList("cisco.base.pod"));
    contextServiceClient.create(pod);
    LOGGER.info("Created Pod: " + pod.getId());

    // Do anything else you want to try here!
    // e.g. create data, update data, search for data

}
/**
 * Create a Custom Connector State Listener to override the stateChanged behavior
 */
public static class CustomConnectorStateListener implements ConnectorStateListener {
    public ConnectorState connectorState;

    public ConnectorState getConnectorState(){
        return connectorState;
    }

    @Override
    public void stateChanged(ConnectorState previousState, ConnectorState newState)
    {
        connectorState = newState;
        LOGGER.info("Connector state changed: " + newState);
        if (newState == ConnectorState.STOPPED) {
            // Perform optional cleanup tasks, etc ...
            LOGGER.info("Connector stopped.");
        }else if (newState == ConnectorState.REGISTERED) {
            // Perform any actions needed once connector is registered, etc ...
            LOGGER.info("Connector started.");
        } else if (newState == ConnectorState.UNREGISTERED) {
            // Perform any actions needed once connector is unregistered, etc ...
            LOGGER.info("Connector unregistered.");
        }
    }
}
/**
 * Before initializing the connector, create and add a ConnectorStateListener to the ContextServiceClient.
 * @param contextServiceClient
 * @return the created ConnectorStateListener
 */
public static CustomConnectorStateListener addStateListenerToContextConnector(ContextServiceClient contextServiceClient){
    CustomConnectorStateListener stateListener = new CustomConnectorStateListener();
    contextServiceClient.addStateListener(stateListener);
    return stateListener;
}
/**
 * Before initializing the connector, create and add a ConnectorStateListener to the Management Connector.
 * @param mgmtConnector
 * @return the created ConnectorStateListener
 */
public static CustomConnectorStateListener addStateListenerToMgmtConnector(ManagementConnector mgmtConnector){
    CustomConnectorStateListener stateListener = new CustomConnectorStateListener();
    mgmtConnector.addStateListener(stateListener);
    return stateListener;
}
/**
 * Wait timeoutSeconds for connector to be initialized, based on state listener callback
 * @param stateListener
 * @param timeoutSeconds
 */
public static void waitForConnectorRegistered(CustomConnectorStateListener stateListener, int timeoutSeconds) throws Exception{
    long startTime = System.currentTimeMillis();
    while((System.currentTimeMillis() - startTime) <= timeoutSeconds*1000 &&
            ConnectorState.REGISTERED.equals(stateListener.getConnectorState())){
        try {
            Thread.sleep(100);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
    if(!ConnectorState.REGISTERED.equals(stateListener.getConnectorState())){
        throw new Exception("Timeout waiting for connector to register.");
    }
}

Helper Methods

The Context Service SDK provides helper methods that make your code more concise, via two classes:

  • SDKUtils
  • DataElementUtils

SDKUtils

Use SDKUtils to get IDs, lastUpdated, and locations from responses:

  • getIdFromResponse(Response)—Returns an ID, as a string, from a Response object. Objects include customerID, podID, and requestId, depending on the class on which it is called.
  • getLastUpdatedFromResponse(Response)—Returns a RFC3339Date from a Response object.
  • getIdFromUrl(url)—Returns an ID, as a string, when you provide a location. For example, from response.getLocation().toString().
/**
 * Get Pod ID from the ClientResponse returned from ContextServiceClient.create()
 * Pod.getId() is preferred!
 * @param contextServiceClient an initialized context service client
 * @param pod a Pod object that hasn't been created in Context Service yet
 * @return the id of the newly created Pod
 */
public static String getIdFromCreatePodResponse(ContextServiceClient contextServiceClient, Pod pod) {
    ClientResponse response = contextServiceClient.create(pod);
    return SDKUtils.getIdFromResponse(response);
}

/**
 * Get Pod ID from the ClientResponse returned from ContextServiceClient.create()
 * Pod.getId() is preferred!
 * @param contextServiceClient an initialized context service client
 * @param pod a Pod object that hasn't been created in Context Service yet
 * @return the id of the newly created Pod
 */
public static String getIdFromCreatePodResponse(ContextServiceClient contextServiceClient, Pod pod) {
    ClientResponse response = contextServiceClient.create(pod);
    return SDKUtils.getIdFromResponse(response);
}

/**
 * Get the lastUpdated timestamp from the ClientResponse returned from ContextServiceClient.create()
 * Pod.getLastUpdated() is preferred!
 * @param contextServiceClient an initialized context service client
 * @param pod a Pod object that hasn't been created in Context Service yet
 * @return the lastUpdated time of the new Pod
 */
public static RFC3339Date getLastUpdatedFromCreatePodResponse(ContextServiceClient contextServiceClient, Pod pod) {
    ClientResponse response = contextServiceClient.create(pod);
    return SDKUtils.getLastUpdatedFromResponse(response);
}

/**
 * Get the lastUpdated timestamp from the ClientResponse returned from ContextServiceClient.create()
 * Pod.getLastUpdated() is preferred!
 * @param contextServiceClient an initialized context service client
 * @param pod a Pod object that hasn't been created in Context Service yet
 * @return the lastUpdated time of the new Pod
 */
public static RFC3339Date getLastUpdatedFromCreatePodResponse(ContextServiceClient contextServiceClient, Pod pod) {
    ClientResponse response = contextServiceClient.create(pod);
    return SDKUtils.getLastUpdatedFromResponse(response);
}

DataElementUtils

Use DataElementUtils work with Java Sets in DataElements. It is less cumbersome to create or modify a Java Map than it is to create or modify a Java Set. convertMapToDataSet() and convertDataSetToMap() helper methods allow you handle DataElements as Maps, while still allowing the interfaces to use them as Sets.

getDataElements() returns a Set. convertDataSetToMap(Set<DataElement> dataElements) takes the set of data elements returned by the .getDataElements method and returns a map.

setDataElements() requires a Set. It is easier to define a Map of key/value pairs to load into a POD, Customer, or Request, than it is to define a Set with the same information.

// examples demonstrating dataElements usage
/**
 * Create a Customer with default fields and fieldsets.
 * @param contextServiceClient an initialized ContextServiceClient
 * @return a newly-created customer with the cisco.base.customer fieldset
 */
public static Customer createCustomerWithBaseFieldset(ContextServiceClient contextServiceClient) {
    Customer customer = new Customer(
            DataElementUtils.convertDataMapToSet(
                    new HashMap<String, Object>() {{
                        put("Context_Work_Email", "john.doe@example.com");
                        put("Context_Work_Phone", "555-555-5555");
                        put("Context_First_Name", "John");
                        put("Context_Last_Name", "Doe");
                        put("Context_Street_Address_1", "123 Sesame Street");
                        put("Context_City", "Detroit");
                        put("Context_State", "MI");
                        put("Context_Country", "US");
                        put("Context_ZIP", "90210");
                    }}
            )
    );
    customer.setFieldsets(Arrays.asList("cisco.base.customer"));
    contextServiceClient.create(customer);
    return customer;
}

/**
 * Update a Customer.
 * @param contextServiceClient an initialized ContextServiceClient
 * @param customerId
 * @return the updated Customer
 */
public static Customer updateCustomer(ContextServiceClient contextServiceClient, UUID customerId) {
    Customer customer = contextServiceClient.get(Customer.class, customerId.toString());
    Map<String, Object> updateData = DataElementUtils.convertDataSetToMap(customer.getDataElements());
    updateData.put("Context_Street_Address_1", "333 Sesame Street");
    customer.setDataElements(DataElementUtils.convertDataMapToSet(updateData));
    contextServiceClient.update(customer);
    return customer;
}

Errors

/**
 * Demonstrate catching and throwing a Get error
 * @param contextServiceClient an initialized ContextServiceClient
 */
public static void throwErrorOnGet(ContextServiceClient contextServiceClient) {
    try {
        String invalidId = "2472ae10-4f8c-11e6-87cb-851eced64b31";
        contextServiceClient.get(Pod.class, invalidId);
    } catch (ApiException e) {
        LOGGER.info("get failed as expected (pod id was invalid): " + e);
        throw e;
    }
}

Most of the Context Service SDK methods throw an ApiException that you can use with a try/catch statement to isolate errors.