Java Reference

Handles

Introduction

In KOS, a handle is a human-readable name for a software object. Handles allow us to identify and communicate with system code. Some objects are associated with specific physical devices, while other objects exist solely for system control and monitoring. A handle exists as a string path, which resembles a filesystem path.

Overview

This section gives a brief overview of handles and their associated classes/interfaces.

Background

In KOS, there is a need for deterministic, unambiguous object identifiers. Benefits include:

  • Human-readable identifiers in logs

  • Use in HTTP endpoints

  • Object-relative persistence

  • Accessing objects by name

  • Configuring objects by name

  • Names are constant between reboots

Properties of Handles

The properties of handles are:

  • a handle uniquely identifies a single software object

  • a handle is a sequence of human-readable tokens separated by periods, called the path

  • tokens consist of a type and name, which are separated by a colon

  • handles are defined by an object graph traversal, which appends tokens

So a handle looks like this:

type1:name1.type2:name2.type3:name3...

This handle is called a path, because it starts at a root node and moves down the object hierarchy, and is analogous to a filesystem path.

Important

While a handle path is a structured string, it is considered opaque, and therefore should not be manufactured, parsed, or trimmed.

All paths are computed by the KOS system during application start up.

Software Components

The software components that comprise handles are:

Handle

Class that provides a building block for allocating a handle to an object.

HandleAware

Interface indicating that an object can be identified by a handle.

ContextHandleAware

Interface that extends HandleAware, for objects that should be children of the handle associated with a BeanContext.

HandleGetter

Interface for fetching beans by handles.

HandleService

System component used to retrieve objects using handles; instantiated using @Autowired.

API Components

This section gives the API for each of the primary handle components.

Tip

These reference articles work in conjunction with the Javadocs. While there is some overlap, our goal is that these web pages go into more detail and explain some of the interesting use cases. Please refer to the Javadocs while going through these discussions.

Handle

The following defines the Handle class:

API: Handle class
public class Handle {

    // Constructors:
    public Handle(Object bean, String name);
    public Handle(Object bean, Class<?> clazz);

    // Get bean:
    public String getBean();

    // Get/set this node's name:
    public String getName();
    public void setName(String name);  // only used if the name was not known at time of construction

    // Get path:
    public String getPath();  // returns full path of this handle, which is calculated by the KOS system

    // Add child nodes:
    public void add(Handle child);
    public void add(String prefix, Handle child);

    // toString:
    public String toString();  // returns same as getPath()
}

HandleAware

The HandleAware interface indicates that an object can be identified by its handle. The context computes the handle for the bean, which is then used as its unique identifier. This means it can be used as a persistent storage key, and can also be used in endpoints to reference the bean symbolically.

API: HandleAware inteface
public interface HandleAware {

    Handle getHandle();

    default String getPath() {
        return getHandle().getPath();
    }

    default String getName() {
        return getHandle().getName();
    }

    default void addHandleChild(String prefix, HandleAware child) {
        getHandle().add(prefix, child.getHandle());
    }
}

ContextHandleAware

Here’s the ContextHandleAware interface:

API: ContextHandleAware interface
public interface ContextHandleAware extends HandleAware {

    // Returns prefix for the object to be used when the handle is allocated by the context:
    default String getHandlePrefix() {
        return getClass().getSimpleName().toLowerCase();
    }
}

HandleGetter

This is the HandleGetter interface:

API: HandleGetter interface
public interface HandleGetter {

    // Get bean of the specified type for the specified handle path:
    <T> T getBean(String path, Class<T> clazz);

    // Get bean with the specified handle path:
    Object getBean(String path);

    // Returns a list of beans of the specified type:
    <T> List<T> getBeans(Class<T> clazz);
}

HandleService

Each handle is computed by the HandleService when it walks the object graph at application start.

The HandleService object:

  • Assigns handles to all HandleAware objects. It walks the object graphs and combines names into handles.

  • Keeps track of all objects by handle, so that any software can look up an object using its handle.

API: HandleService component
public final class HandleService {

    // Returns a root handle with the specified name:
    public Handle getRootHandle(String name);

    // Returns the list of handles in the system:
    public Collection<String> getHandles();

    // Gets the bean of the specified type for the specified handle path:
    public <T> T getBean(String path, Class<T> clazz);

    // Gets the bean with the specified handle path:
    public Object getBean(String path);
}

To use the HandleService, autowire it into your code:

MyClass class uses the HandleService component
public class MyClass {

    @Autowired
    private HandleService handleService; (1)

    public void someMethod() {
        Collection<String> handles = handleService.getHandles(); (2)
    }
}
1 Autowire the HandleService object into your custom class
2 Access it in your class’s code

Example Code

We’ll run through examples to demonstrate some of the features of handlers.

In the next two sections we’ll create a) a simple service, and b) our app note framework module class.

Source code for this and most other Java reference pages is available on GitHub.

PaymentService

As a backend developer, the most common code you’ll develop are services. Services are the core of system control and monitoring. As such, KOS includes two base classes that all services should be derived from: either AbstractService or AbstractConfigurableService.

In this example we’re not interested in configuration properties, therefore we’ll derive a service named PaymentService from AbstractService. In the config articles we’ll discuss the configuration equivalent.

Deriving PaymentService from AbstractService
package com.example.handles;

import com.tccc.kos.commons.core.service.AbstractService;
import lombok.extern.slf4j.Slf4j;

@Slf4j
public class PaymentService extends AbstractService {

    public void sayHello() {
        log.info("> Hello from PaymentService");
    }
}

HandlesModule

Again, in our app note framework, we add the following code:

HandlesModule class
package com.example.handles;

import com.example.Module;
import com.tccc.kos.commons.core.context.BeanContext;
import com.tccc.kos.commons.core.context.annotations.Autowired;
import com.tccc.kos.commons.core.service.handle.HandleService;
import java.util.Collection;
import lombok.extern.slf4j.Slf4j;

@Slf4j
public class HandlesModule implements Module {

    @Autowired
    private HandleService handleService;

    @Override
    public void init(BeanContext ctx) {
        log.info("> HandlesModule.init()");
        ctx.add(new PaymentService());
    }

    @Override
    public void run() {
        log.info("> HandlesModule.run()");
        showAllHandles();
        accessMyServiceViaHandle();
    }

    private void showAllHandles() {
        log.info("> showAllHandles()");
        Collection<String> handles = handleService.getHandles();
        log.info("> handles: {}", handles);
    }

    private void accessMyServiceViaHandle() {
        log.info("> accessMyServiceViaHandle()");
        String path = handleService.getBeans(PaymentService.class).get(0).getPath();
        log.info("> path: {}", path);
        PaymentService paymentService = (PaymentService)handleService.getBean(path);
        paymentService.sayHello();
    }
}

In our SystemApplication, handles and paths are computed by the KOS system and are ready with the start() method is called, which corresponds to our HandlesModule.run() method.

MyKosApp

Be sure to modify this line in the MyKosApp to instantiate the HandlesModule:

MyKosApp change
public class MyKosApp extends SystemApplication<BaseAppConfig> {

    public void initModule() {
        module = new HandlesModule();
    }

    // . . .
}

Ex: View all system handles

This example found in HandlesModule demonstrates how to retieve the list of all handles in the system:

Grab list of handles
    // In HandlesModule class:
    private void showAllHandles() {
        log.info("> showAllHandles()");
        Collection<String> handles = handleService.getHandles();
        log.info("> handles: {}", handles);
    }

First thing to do after adding this code is to set a breakpoint at the log statement in the showAllHandles() method. Now debug the program using your IDE. When the breakpoint is hit, take a look at the handles value. It should look something like this:

debug list of all handles
Figure 1. List of all handles as viewed in the IDE debugger

Note the path system.service:payment. Let’s look at the pieces of this path:

  • system : the root of your KOS system application

  • service:payment : a standard KOS service, whose name is "payment"; notice that the "Service" suffix was removed from the class name

You’ll also see:

  • system.app : your system application object

  • kos.service:handle : the HandleService object instantiated with the @Autowired annotation in the above code

  • kos.service:* : a large number of built-in KOS services that can be autowired and used in your application

The log output of this code is:

HandlesModule.showAllHandles() output
> ====== BEGIN =========================================
> MyKosApp.load()
> HandlesModule.init()
> MyKosApp.start()
> HandlesModule.run()
> showAllHandles()
> handles: [kos.service:kab, kos.service:nodeMgr, kos.service:ingredient, kos.service:apiClient,
 kos.service:deviceState, kos.service:app, kos.service:storage, kos.service:extension, kos.service:trouble,
 system.service:payment, kos.service:insertion, kos.service:udev, kos.service:nozzle, kos.service:assignment,
 kos.service:spawn, kos.service:serial, kos.service:config, kos.service:fuse, kos.service:finder,
 kos.service:fuseLogger, kos.service:assembly, kos.service:future, kos.service:brokerRouterBridge,
 kos.service:hardware, kos.service:insertion.filter:ingredientFilter, kos.service:messageBroker,
 kos.service:holder, system.app, kos.service:handle, kos.service:region, kos.service:firmware,
 kos.service:blink, kos.service:chrome]
> ====== END ===========================================

If you want to see this list of handles from the outside, issue the following HTTP GET:

http://localhost:8081/api/kos/handles
postman list of all handles
Figure 2. List of all handles as viewed in Postman

To see a particular object, try this:

http://localhost:8081/api/kos/handles/system.service:payment
postman system service payment
Figure 3. Our custom PaymentService

Ex: Grab object using handle

This example found in HandlesModule demonstrates how to grab an object with its handle:

Obtain reference to an object using its handle path
    // In HandlesModule class:
    private void accessMyServiceViaHandle() {
        String path = handleService.getBeans(PaymentService.class).get(0).getPath(); (1)
        log.info("> path: {}", path); (2)
        PaymentService paymentService = (PaymentService)handleService.getBean(path); (3)
        paymentService.sayHello(); (4)
    }
1 Since we don’t have the handle path at this point, we look it up using the HandleService
2 The path is logged, which is system.service:payment
3 Using the acquired path, we use the HandleService to retrieve the corresponding bean
4 Once the reference is obtained, we can make calls to it

Here’s the log output:

HandlesModule.accessMyServiceViaHandle() output
> ====== BEGIN =========================================
> MyKosApp.load()
> HandlesModule.init()
> MyKosApp.start()
> HandlesModule.run()
> accessMyServiceViaHandle()
> path: system.service:payment (1)
> Hello from PaymentService (2)
> ====== END ===========================================
1 The handle path is retrieved
2 After the PaymentService is retrieved using its handle, its sayHello() method is called

Summary

In this article, we:

  • Looked at how KOS allows for naming important Java objects.

  • Learned that an object’s name is known as its "handle", which is expressed as a "path".

  • Created a dummy service and saw how KOS automatically named it.

  • Saw how to use the HandleService system component to view the list of handles and retrieve objects using handles.

Handles are very important in the KOS configuration system, which is studied in another article.

Previous
Next
On this page
Java Development
Seamlessly transition from Legacy+ systems to Freestyle microdosing and advanced distributed dispense systems.
UI Development
Using KOS SDKs, integrating Consumer and Non-consumer facing UIs becomes seamless, giving you less hassle and more time to create.
Video Library
Meet some of our development team, as they lead you through the tools, features, and tips and tricks of various KOS tools.
Resources
Familiarize yourself with KOS terminology, our reference materials, and explore additional resources that complement your KOS journey.
Copyright © 2024 TCCC. All rights reserved.