The Latest version of documentation can be found on Glue42 Docs.

Using Java Glue42 Library

The Java Glue42 library provides an easy way to make your enterprise Java applications Glue42 enabled.

Installation

You can use java-glue42 in the same way as any standard Java library.

Java Glue42 requires JDK 8+(Java SE 8+) and is JDK 9+ ready.

Maven

<?xml version="1.0" encoding="utf-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.example</groupId>
    <artifactId>myproject</artifactId>
    <version>0.0.1-SNAPSHOT</version>

    <properties>
        <maven.compiler.source>1.8</maven.compiler.source>
        <maven.compiler.target>1.8</maven.compiler.target>
    </properties>

    <dependencies>
        <dependency>
            <groupId>com.glue42</groupId>
            <artifactId>java-glue42-shaded</artifactId>
            <version>1.4.5</version>
        </dependency>
    </dependencies>
</project>

Gradle

apply plugin: 'java'

sourceCompatibility = 1.8
targetCompatibility = 1.8

dependencies {
    compile 'com.glue42:java-glue42-shaded:1.4.5'
}

Quick Start

import com.tick42.glue.Glue; (1)
try (Glue glue = Glue.builder().build()) (2)
{
    System.out.println(glue.version()); (3)
}
1 First import glue package
2 Glue is the main entry point of the sdk. It is thread-safe and you should create a single instance (per glue application) and share it throughout your code
3 We get the Glue version string

Always close the Glue instance once you are done with it, in order to free up underlying resources (ws connections, thread pools…​). This example uses try-with-resources block because Glue extends AutoCloseable, in a real application, you will probably call close or closeAsync explicitly.

Configuration

To configure Java Glue42 you can use .conf files, .properties files, .json files and system properties to externalize your configuration.

Here is an example minimal glue.conf file:

glue {
  application: "My Java App"
}
Glue.builder().withApplicationName("My Java App").build();

Application Definition

To add your Java application to the Launcher application you need to define a configuration file and add it to application config store. By default when running in file mode the store is located at %LocalAppData%\Tick42\GlueDesktop\config\apps.

Here is an example:

[
  {
    "title": "Java Example",
    "type": "exe", (1)
    "name": "java-example",
    "icon": "https://enterprise-demos.tick42.com/resources/icons/w2.jpg",
    "details": {
      "path": "", (2)
      "command": "java", (3)
      "parameters": "-jar example.jar", (4)
      "mode": "tab"
    }
  }
]
1 type should be exe
2 path is the path to the application - relative or absolute
3 command is the actual command to execute (java)
4 parameters holds command line arguments
Application definitions should be a valid JSON file, you should either use forward slash or the escape backslash

You can find detailed information about the application definitions in Configuration documentation.

Application Shutdown

When initializing Glue42, you can pass an event listener that will notify your application when it should be shutting down. This is useful when your app/process is not started directly by Glue42, but rather by Glue42 invoking a script/batch file or another application that in turn starts your application. With this listener you can properly shut down your application, free resources, etc.

import com.tick42.glue.Glue; (1)

Glue.builder()
        .withShutdownRequestListener(glue -> {
            System.out.println("Starting shutdown procedure...");
            glue.closeAsync();
        }) (2)
        .build();
1 First, import the Glue42 package.
2 Pass an event listener for when Glue42 is about to shut down.

Interop

Overview

The Interop API enables applications to:

  • offer functionality to other applications (JavaScript and native) by registering Interop methods

  • discover applications which offer methods

  • invoke (call) methods on the user’s desktop and across the network

  • stream and subscribe to real-time data using the streaming API

Applications that offer methods and/or streams are called Interop servers, and applications which consume them Interop clients, and collectively - Interop instances.

Instance

Any application instance is identified by its Interop Instance, which is a set of known key-value pairs, uniquely identifying an application.

Here’s an example:

Key Example Description

application

Client Portfolio

Application name

region

TICK42

e.g. EU, US, AP

environment

DEMO

e.g. DEV, SIT, UAT, PROD

machine

Lambda

User’s machine name

pid

2864

Process ID

user

duke

Currently logged user

Method Registration

Register Methods

To offer a method to other applications call glue.interop().register(), passing method’s definition and a callback to handle client invocations.

glue.interop().<Map<String, Object>, Map<String, Object>>register(
        MethodDefinition.builder("Sum").withSignature("int a, int b", "int answer").build(),
        (arg, caller) -> {
            int a = (Integer) arg.get("a");
            int b = (Integer) arg.get("b");
            return Collections.singletonMap("answer", a + b);
        });

Once a method is registered, it can be invoked from any app (web, native or java), that is Glue-enabled.

Method Definition

The method definition describes the Interop method your application is offering. It has following properties, required ones in bold:

Name Description

name

Name of the Method, e.g. OpenClientPerformance

accepts

Signature, describing the parameters that the method expects

returns

Signature, describing the properties of the object the method returns

displayName

Human-readable name of the method, e. g. Open Client Performance

description

Description of what method does, useful for documentation purposes, e.g. Launches or activates the Client Performance application

objectTypes

Entities this method is meant to work on, e.g. party(client, prospect, lead), instrument, order etc.

It’s a good idea to specify displayName and description when defining a method. These can be used by generic UI or by your own applications.

Signatures

The signature of a method is a comma-delimited string of parameters, defined as follows:

type [array_modifier] [optional_modifier] parameter_name [composite_schema] [description]

Where type is one of Bool, Int, Long, Double, String, DateTime, Composite

Signature Explanation

String pId, String? dynamicsId

pId is required, dynamicsId is optional

String branchCode, String[] gIds

branchCode and gIds are required, gIds is an array of strings

Composite: {String first, String last} name

name is a composite parameter, and its schema is defined by 2 required strings - first and last

Composite is a structure which contains one or more fields of type:

  • scalar (bool, int, etc)

  • array of scalars

  • a composite (nested structure)

  • an array of composites

Using Composite you can define almost any non-recursive, non-self-referential strucuture.

Asynchronous results

glue.interop().registerAsync("getQuote", (arg, caller) -> {
    CompletableFuture<Map<String, Object>> future = doSomethingAsync(arg); (1)
    return future;
});
1 doSomethingAsync() returns a future

Method Invocation

Invoking Methods

To invoke a method offered by other applications, call glue.interop().invoke(), passing method’s name and arguments. Then use the returned future to receive result or an error.

Map<String, Object> arg = new HashMap<>();
arg.put("a", 37);
arg.put("b", 5);
glue.interop().invoke("Sum", arg)
        .thenAccept(result ->
                            result.getReturned()
                                    .ifPresent(r -> System.out.println(r.get("answer"))))
        .toCompletableFuture().join();

Multiple Responses

Invoking a method on multiple Interop instances produces multiple responses.

This is how you can iterate over all of the responses:

result.forEach(r -> {
    if (r.getStatus().isSuccess())
    {
        System.out.printf("success:%s%n", r.getReturned().orElseGet(Collections::emptyMap));
    }
    else
    {
        System.out.printf("error:%s%n", r.getStatus().message().orElse(null));
    }
});
When invocation result has multiple responses the result’s hasMultipleResponses() will return true.

Discovery

Discovering Methods

To list all available methods from all servers

System.out.println(glue.interop().getMethods());

Searching for Methods

include::{example-code}/InteropExample.java[tag=query,indent=0]}

Discovering Servers

To list all servers offering methods

System.out.println(glue.interop().getServers());

Streaming

Overview

Interop stream can be used by your application to:

  • publish events which can be observed by other applications or provide real-time data (e.g. market data, news alerts, notifications etc.) to other applications, by publishing to Interop stream

  • receive and react to the above events and data by creating an Interop stream subscription

We call applications which create and publish to Interop Streams publishers, and applications which subscribe to Interop Streams subscribers. An application can be both.

Interop Streams are used extensively in Glue42 Desktop products and APIs.

Receiving data

Subscribing to a Stream

Subscribing to a streaming method is achieved by invoking glue.interop().stream().

glue.interop()
        .stream("MarketData.LastTrades",
                Collections.singletonMap("symbol", "ORCL"))
        .thenAccept(stream -> stream.subscribe(new StreamSubscriber<Map<String, Object>>() {
            @Override
            public void onData(ServerMethod method, Map<String, Object> data) {
                // do something with the data
            }
        }));
Closing Stream Subscription

To close a stream subscription invoke close() or closeAsync() method on the subscription reference returned by subscribe() method.

AsynchronousCloseable subscription =
        stream.subscribe(new StreamSubscriber<Map<String, Object>>() {});
subscription.closeAsync();
Getting Notified when a Subscription is Closed

At any time, a stream subscription can be closed either because the publisher shutting down or due to an error.

stream.subscribe(new StreamSubscriber<Map<String, Object>>()
{
    @Override
    public void onSubscribe(StreamSubscription subscription)
    {
        subscription.onClose().thenRun(() -> {
            // called when the subscription is closed
        });
    }

    @Override
    public void onFailed(ServerMethod method, String reason)
    {
        // called if the subscription request is rejected
    }
});

Publishing Data

To start publishing data, you need to register an Interop Stream by calling glue.interop().register() and provide a method definition and stream subscription request handler.

glue.interop()
        .register(MethodDefinition.forName("Clock"),
                  StreamSubscriptionRequestHandler.accept())
        .thenAccept(stream -> {
            Map<String, Object> data =
                    Collections.singletonMap("CurrentTime", Instant.now().toEpochMilli());
            // will send data to all branches, as no branch is specified
            stream.send(data);
        });

StreamSubscriptionRequestHandler.accept() will accept all subscription request on the default branch (more on that later).

Handling Subscription Requests

To control how you application accepts or rejects stream subscription requests by specifying custom handler.

This handler receives the request as argument and must return a StreamConsumer instance via invoking accept(), acceptOn() or reject() methods of the request.

glue.interop()
        .<Map<String, Object>>register(
                MethodDefinition.builder("MarketData.LastTrades")
                        .withObjectType("Symbol")
                        .build(),
                request -> {
                    String app = request.getCaller().getApplication();
                    String symbol = (String) request.getArg().get("Symbol");

                    if (symbol != null)
                    {
                        System.out.printf("Accepting %s subscription on %s%n", app, symbol);
                        return request.acceptOn(symbol);
                    }
                    else
                    {
                        System.out.printf("Rejecting %s symbol not specified%n", app);
                        return request.reject("Symbol not specified");
                    }
                }
        );
Handling New or Removed Consumers

To track when a consumer is added and/removed StreamSubscriptionRequestHandler provides onAdded() and onRemoved() sugar methods to compose callbacks.

glue.interop()
        .register(
                MethodDefinition.builder("MarketData.LastTrades")
                        .withObjectType("Symbol")
                        .build(),
                StreamSubscriptionRequestHandler
                        .<Map<String, Object>>accept((arg, caller) -> (String) arg.get("Symbol"))
                        .onAdded(consumer -> {
                            // called when new consumer subscription is accepted
                        })
                        .onRemoved(consumer -> {
                            // called when a consumer subscription is removed
                        })
        );

In order to use those methods you need an instance of type StreamSubscriptionRequestHandler. One way to achieve this is to not use lambda but one of the static factory methods provided.

Following fragment creates a handler that is equivalent (if you ignore logging) with the one provided in a previous section.

StreamSubscriptionRequestHandler
        .<Map<String, Object>>accept((arg, caller) -> (String) arg.get("Symbol"))

StreamConsumer reference can be used to

  • access request parameters and caller instance - getArg() and getCaller()

  • inspect branch on which consumer was accepted - getBranch()

  • push data directly to a consumer (unicast) - send()

  • close the subscription forcefully - close() or closeAsync()

Multicast Using Branches

A single stream supports one or multiple named sub-streams that are called branches. In such cases, where it doesn’t make sense for a stream to be split into multiple sub-streams a default branch is used.

The stream reference is of type StreamProducer and can be used to:

  • list all available branches:

Map<String, StreamBranch> allBranches = stream.getBranches();
  • list all stream consumers, regardless of the branch they are on:

List<StreamConsumer<?>> allConsumers = stream.getConsumers();

If your stream publishing code uses branches (e.g. creates a branch for each unique set of subscription arguments and associates the consumers with that branch), whenever a data arrives from your underlying source, you’ll want to use the branch to publish data instead of manually walking all consumers and sending data to the interested clients.

String branchKey = "Providers";
StreamBranch branch = stream.getBranches().get(branchKey);

if (branch != null)
{
    branch.push(data);
    System.out.printf("Pushed data to all subscribers on branch \"%s\"!%n", branch.getKey());
}
else
{
    System.out.printf("Branch \"%s\" does not exist!%n", branchKey);
}

Stream Discovery

Streams are special Interop methods with isSupportsStreaming() returning true.

Intents

Overview

See docs.

The Intents API is accessible through glue.intents().

Finding Intents

To find all registered intents, use the all() method:

glue.intents().all();

Raising Intents

To raise an Intent, use the raise() method:

glue.intents().raise("ShowChart");

The raise() method accepts an Intent name as a String or an IntentRequest as argument.

Target Intent Handlers

When raising an Intent, optionally target one or more IntentHandler instances using the withTarget() method of the IntentRequest builder:

Intent intent = glue.intents().all()
        .toCompletableFuture().join()
        .get("ShowChart");

IntentHandler intentHandler = intent.getHandlers().get(0);

IntentRequest intentRequest =
        IntentRequest.intent(intent.getName())
                .withTarget(IntentTarget.application(intentHandler.getApplicationName()))
                .build();

glue.intents().raise(intentRequest);

The IntentTarget can be created using one of the following factory methods:

  • startNew() - will start a new instance of the first available Intent handler;

  • reuse() - will reuse the first available running instance of an Intent handler or fallback to startNew() if there are no running instances available;

  • application(String) - will start a new instance of a specific Intent handler application;

  • instance(String) - will reuse a specific running instance of an Intent handler;

The default value for the Intent request target is startNew() when an Intent Handler application is available. If the Intent has only been registered dynamically, the default value is reuse().

Context

Passing Initial Context

To pass initial context to the Intent handler, use the withContext(String, T) method of the IntentRequest builder.

Map<String, Object> contextData =
        Collections.singletonMap("data",
                Collections.singletonMap("selectedClientID", 1));

Map<String, Object> options = new LinkedHashMap<>();
options.put("width", 300);
options.put("height", 200);

glue.intents().raise(
        IntentRequest.intent("ShowChart")
                .withTarget(IntentTarget.startNew())
                .withContext(
                        "Instrument", // contextType
                        contextData)
                .withOptions(options)
                .build()
);

The withOptions(Map<String,Object>) method of the IntentRequest builder is used to pass custom application startup options to the Intent handler.

Handling Context Updates

To handle the context data passed when an Intent is raised and targeted at your application, use the addIntentListener(String, IntentContextHandler) method.

glue.intents().addIntentListener("ShowChart", context -> {
    context.getType().ifPresent(contextType -> {
        switch (contextType) {
            case "Instrument": {
                Map<String, Object> data = context.getData();
                // Application specific logic for handling the new context data
                break;
            }
        }
    });
    return Collections.emptyMap(); // optionally return result to the caller
});

Registering Intents at Runtime

To register an Intent at runtime, use addIntentListener(IntentListenerRequest, Function<IntentContext, T>) method.

IntentListenerRequest<Map<String, Object>> intent =
        IntentListenerRequest.intent("ShowChart", ReifiedType.OBJECT_MAP)
                .withContextTypes(Collections.singletonList("Instrument"))
                .withDisplayName("Instrument Chart")
                .build();

glue.intents().addIntentListener(intent, (context) -> {
    context.getType().ifPresent(contextType -> {
        switch (contextType) {
            case "Instrument": {
                Map<String, Object> data = context.getData();
                // Application specific logic for handling the new context data
                break;
            }
        }
    });
    return Collections.emptyMap(); // optionally return result to the caller
});

Window Management

Overview

Sticky Windows allows to make windows sticky, so they can stick together forming groups that can move, minimize and maximize together.

The window management API is exposed in glue.windows(), and gives following features, many of which not found in traditional Java GUI frameworks:

  • create metro/flat or tab windows

  • control and customize the chrome of these windows

    • visibility: e.g. create hidden windows, show them later

    • size: set minimum and/or maximum bounds

    • control what the user can do with the windows, e.g. allow a window to be sticky

    • add custom buttons and react to user clicking these

    • organize windows into tabs which the user can tear off

Making a Window Sticky

Registering a Swing Window

WindowManager windows = glue.windows();
WindowHandle<JFrame> handle = windows.getWindowHandle(frame); (1)
windows.register(handle) (2)
        .thenAccept(window ->
                frame.addWindowListener(new WindowAdapter()
                {
                    @Override
                    public void windowClosing(WindowEvent e)
                    {
                        window.closeAsync(); (3)
                    }
                }));
1 Get window handle
2 Register window
3 Unregister window on close

Currently, Glue42 Windows requires the underlying native handle (hwnd in case of Windows) to be initialized and attached to the JFrame before the window can be registered. This is now achieved internally through Glue32 Windows.

Window Types

Glue supports following window modes: flat and tab.

The window type is controlled by the mode window option, which can be specified in the application definition or during the register() call

glue.windows().register(handle, options -> options.mode(WindowMode.FLAT));

Controlling the Window

Once an application window is registered, Glue42 Windows will accept full control over the window positioning, sizing and visibility. The application shouldn’t use native methods (for example, Swing calls) to control the window as it will interfere with the Glue42 window management.

Glue provides several SW aware methods that should be used when direct control is required:

Title

Change the window title:

window.changeTitle("New Title");

Size and position

Change the window bounds:

window.changeBounds(new Bounds(10, 10, 200, 200));

Visibility

Note that changing the window visibility also affects its associated icon.

window.changeVisibility(false);

Frame Buttons

You can put extra buttons in the frame area of the window and handle clicks for those buttons.

Adding Button

Use addFrameButton method to add a new button:

window.addFrameButton("search-button",
                      ButtonOptions.builder()
                              .toolTip("Search")
                              .order(1)
                              .image(new byte[0]) // needs to be a valid image
                              .build())
        .thenRun(() -> System.out.println("created button"));

Removing Button

Use removeFrameButton to remove a button from the frame:

window.removeFrameButton("search-button")
        .thenRun(() -> System.out.println("removed button"));

Handling Clicks

Use onFrameButtonClicked to subscribe for button clicks:

window.onFrameButtonClicked(e -> {
    if ("search-button".equals(e.getButtonId()))
    {
        System.out.println("Search button clicked");
    }
});

Window hibernation/resume

You can subscribe for when the layout of your application is suspended/hibernated, so you can for example limit your resource usage while your application isn’t being used. You can also subscribe when your application is restored/resumed, so you can bring it back to it’s functional state. An example of limiting your resource usage could be for you to unsubscribe from receiving data updates and such.

With both events you’ll get the layout name and the meta data for that layout, if any.

Handling Layout Hibernation

window.onHibernated(e -> System.out.println("Hibernating the layout with name " + e.getLayout()));

Handling Layout Resume

window.onResumed(e -> System.out.println("Resuming the layout with name " + e.getLayout()));

Application Management

Overview

The Application Management API provides a way to manage Glue42 Desktop applications. Starting an application through the API returns an instance object that can be used to manage it.

ApplicationInstance - A running copy of an application within Glue42. The API provides a way to stop an application instance.

Registering a function through the API allows you to react when glue desktop wants to start a child application instance. Usually you will register a new window through the Windows API.

ApplicationInstanceHandler - A handler function that will be invoked when child application needs to be started.

Listing Applications

To list all available applications for the current user, use the applications() method:

glue.appManager().applications();

The applications() method returns a Map<String, ApplicationInstance> result containing the available instances keyed by application name.

Starting applications

To start an application use the start() method:

glue.appManager().start("clientlist")
        .whenComplete((instance, error) -> {
            if (error != null) {
                // application failed to start
            }
        });

You can also pass context (an application-specific object that will be available in the new app) or override any of the pre-configured window settings:

glue.appManager().start("clientcontact", Collections.singletonMap("selectedUser", 2));

Listing running instances

To list all running instances of an application, use the instances() method:

glue.appManager().instances();

The instances() method returns a Collection<ApplicationInstance> result containing all running application instances.

Stopping instances

To stop a running instance use close() or closeAsync() method:

instance.closeAsync();

Multi Window Apps

Java Glue42 offers support for applications consisting of multiple windows. Each child window can be registered in the context of a child Glue42 Application that you can save and restore in a Layout, start directly from the Glue42 toolbar, etc.

The following example demonstrates how to register an ApplicationInstanceHandler using the registerInstanceHandler() method. It will be invoked when a child window of the specified application is started. The handler in the example registers the child window as a Glue42 Window using a WindowRegistration builder. When the child window has been registered, it starts to listen for context updates using its onContextUpdated() method. Finally, when a save is requested, the child window will save its current context using the addSaveListener() method in order to be able to retrieve it later when the Layout is restored:

glue.appManager().registerInstanceHandler("my-child-window", applicationInstance -> {

    glue.windows().register(
            WindowRegistration.builder(glue.windows().getWindowHandle(childFrame))
                    .withInstanceId(applicationInstance.getId())
                    .build()
    ).thenAccept(window ->
            window.onContextUpdated(e ->
            {
                int selectedIndex = (Integer) e.getContext().getOrDefault("SelectedIndex", -1);
                selector.setSelectedIndex(selectedIndex);
            }));
    glue.layouts().addSaveListener(applicationInstance.getId(), request ->
            Collections.singletonMap("SelectedIndex", selector.getSelectedIndex()));
});

Layouts

Layouts Overview

Layouts allow you to save the layouts of any set of applications running in Glue Desktop as a named layout and later restore it.

You can access the API through the following entry point:

glue.layouts();

Global Layouts

Global saving and restoring is an operation in which all applications running on a user’s desktop are saved to a named Layout which can later be restored.

Saving a Global Layout

To save a global Layout use glue.layouts().save() method and specify a name for the Layout:

CompletionStage<Void> layout = glue.layouts().save(options -> options
        .withName("Name of Layout")
        .withType(LayoutType.GLOBAL)
);
If a Layout with this name already exists it will be replaced. If a name is not specified, a random name will be generated.

Restoring a Global Layout

To restore a global Layout, use the restore() method:

LayoutRestoreOptions restoreOptions = LayoutRestoreOptions.builder("LayoutToRestore").build();
CompletionStage<Void> restore = glue.layouts().restore(restoreOptions);

Application’s default layouts

When an application is started from the Toolbar, the initial size and position is defined in the application properties. Users often want to place it at an alternative position and so move it. GlueDesktop remembers the last location of a window and use that as the new start position.

Saving the last position is enabled by default for all applications (single windows or activities), but can be disabled per application by configuration.

Also if the user holds Shift key while starting an application it will appear on default screen position (as defined in properties) – this does not require change in AppManager.

Managing Layouts

Listing Layouts

To get a collection of all Layouts, use the list() method:

Collection<Layout> layouts = glue.layouts().list();

Exporting Layouts

To export all Layouts for the current user, use the exportLayouts() method:

CompletionStage<Collection<Layout>> exportedLayouts = glue.layouts().exportLayouts();

Importing Layouts

You can import collections of Layouts by either merging them with the existing ones, or replacing the existing ones. Use the importLayouts() method and pass a collection of Layouts and import mode (MERGE or REPLACE):

CompletionStage<Void> importLayouts = glue.layouts().importLayouts(Collections.emptyList(), LayoutImportMode.REPLACE);

Saving Custom Data

Application can store custom data ina saved Layout. When the Layout is restored the custom data is also restored and returned to the applications.

Currently, the custom data can only be the window context. When the Layout is restored, the context of the window in the Layout will also be restored if it has been saved previously.

To save custom data, applications can subscribe for Layout save requests using the addSaveListener(). The LayoutSaveHandler passed as argument will be invoked when a Layout save is requested. The handler receives a LayoutSaveRequest object as an argument from which the layout name and type can be extracted. It must return a Map<String, Object> containing pairs of context property names and their values:

Map<String, Object> context = new HashMap<>();
context.put("gridWidth", 420);
context.put("gridHeight", 42);
glue.layouts().addSaveListener((request) -> context);

Channels

Overview

The Channels API enables users to dynamically group windows, instructing them to work over the same shared data.

When two windows are on the same channel they share a context data, which they can monitor and/or update.

A context data object can contain different types of data, e.g. RIC symbol, ClientID and AccountID

Map<String, Object> data = new LinkedHashMap<>();
data.put("RIC", "BMW.GR");
data.put("ClientID", 235399);
data.put("AccountID", "X2343");

Enable Channels for a Window

To add the channel selection to your window you need to enable the channel window option:

glue.windows().register(handle, options -> options.channel())

Create Channel Context

ChannelContext<Map<String, Object>> channelContext = glue.channels().create(window);

Subscribe for Channel Data

When your application wants to track the current channel and its data it should use the subscribe() method:

channelContext.subscribe((ChannelContextDataSubscriber<Map<String, Object>>) (channel, data) -> {
    // each time channel context data is updated this method will be invoked
});

Publish Channel Data Update

To update the current channel context data use the publish() method:

channelContext.publish(Collections.singletonMap("RIC", "VOD.L"));

Shared Contexts

Listing All Available Contexts

A shared context object is a Map containing cross application data. You can access all available context objects in order to manipulate them:

Subscribing for a Context

To subscribe for shared context updates, use the glue.contexts().subscribe() call, passing the name of the desired context object:

glue.contexts().subscribe("app-styling")
        .thenAccept(context -> context.data(data -> {
            // use context data here
        }));

If the specified shared context object does not exist, it will be created. In the example above, the changes to the context object can be handled in the context.data(data → {}) callback.

Updating a Context

You can also update a shared context object by using glue.contexts().update() and passing as a first parameter the name of the context object you want to update, and as a second parameter - a Map<String, Object> with the delta values:

glue.contexts().update("app-styling", Collections.singletonMap("backgroundColor", "red"));

If the key you pass in the Map object exists in the shared context object, it will be overwritten with the new value. If it does not exist, it will be created. If the value of a key in the Map you pass to glue.contexts().update() is null, then that key will be deleted from the shared context object.

Let’s say you have subscribed to a shared context named "app-styling", which already has two entries - "backgroundColor", "red" and "alternativeColor", "yellow". In the example below:

  • "backgroundColor" will be overwritten with a new value - "blue";

  • "alternativeColor" will be deleted from the shared context object;

  • "borderColor" will be created as a new key in the shared context object and its value will be set to "grey";

glue.contexts()
        .subscribe("app-styling")
        .thenAccept(context -> {
            context.data(data -> {
                // use context data here
            });

            Map<String, Object> delta = new HashMap<>();
            delta.put("backgroundColor", "blue");
            delta.put("alternativeColor", null);
            delta.put("borderColor", "grey");

            context.update(delta);
        });

Notifications

Raising Notifications

To raise a notification on user’s desktop, first create a Notification instance

Notification alert = glue.notifications()
        .create("Example", notification -> notification.title("Hello from Java Glue42"));

and then call the raise() method

alert.raise(NotificationSeverity.LOW);

Actions

Notifications can contains actions (usually displayed as buttons in the UI) that the user can execute when he/she sees the notification. Executing an action results in invoking an interop method. The interop method can be registered by the notification publisher or any other application that can handle the action.

The handler of the interop action can also receive parameters, specified by the publisher of the notification.

In following example we add Accept and Reject actions, passing an id parameter to the first action:

glue.interop().register("NotificationAccepted", (arg, caller) -> Collections.emptyMap());
glue.interop().register("NotificationRejected", (arg, caller) -> Collections.emptyMap());
glue.notifications()
        .create("Example", notification -> notification
                    .action("NotificationAccepted", "Accept", Collections.singletonMap("id", 42))
                    .action("NotificationRejected", "Reject"));
java actions

Toast Click

When raising a notification you can specify what happens when the user clicks on the notification toasts. By default this will show the build-in notification details view, but you can replace that with invoking an interop method.

In following example, when user clicks on the notification toast the DetailsHandler interop method will be invoked

glue.notifications()
        .create("Example",notification -> notification.details("DetailsHandler"));

Office

Outlook

Prerequisites

The Outlook integration requires the Glue42 Outlook addin to be installed and running.

Composing Emails

While there’s no technical limitation for the Glue Outlook addin to send an email, there are many reasons while it’s not a good idea. So whenever we are talking about creating a new email, we mean that Glue for Outlook will create a new email window and populate it, but it will not send the email automatically and will instead let the user press the Send button.

To compose a new email use the compose method of the Outlook interface.

glue.outlook().compose(message -> message
                   .to("test@test.com")
                   .cc("someone@else.com")
                   .subject("This is simply a test")
                   .body("<p>Hello from <b>Java Glue42</b></p>"));

Appendices

Appendix A: Reference Configuration

glue {
  # A name that identifies the glue application instance created.
  #application: ""
  gateway: {
    # The url to use when connecting to the gateway.
    url: "ws://127.0.0.1:8385/gw"
    ws: {
      # The maximum message size.
      max-message-size: "100 MiB"
      # The maximum pool size.
      # Defaults to max(3, number of available processors)
      #max-pool-size:
    }
  }
  auth: {
    # The authentication provider.
    # Allowed values are "" and win
    provider: ""
    # The authentication method.
    # Allowed values are secret, access-token and gateway-token
    method: secret
    # Configuration for the secret method
    username: ${user.name}
    password: ""
    # Configuration for access-token and gateway-token methods
    // token: ""
  }
  region: "DEMO"
  region: ${?GLUE-REGION}
  environment: "T42"
  environment: ${?GLUE-ENV}
  interop: {
    instance: true
  }
  contexts: true
  windows: {
    sticky-agent: ${glue.region}-${glue.environment}
  }
}