type1:name1.type2:name2.type3:name3...
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.
This section gives a brief overview of handles and their associated classes/interfaces.
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
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.
The software components that comprise handles are:
Class that provides a building block for allocating a handle to an object.
Interface indicating that an object can be identified by a handle.
Interface that extends HandleAware, for objects that should be children of the handle associated with a BeanContext.
Interface for fetching beans by handles.
System component used to retrieve objects using handles; instantiated using @Autowired.
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. |
The following defines the Handle class:
Handle
classpublic 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()
}
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.
HandleAware
intefacepublic 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());
}
}
Here’s the ContextHandleAware interface:
ContextHandleAware
interfacepublic 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();
}
}
This is the HandleGetter interface:
HandleGetter
interfacepublic 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);
}
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.
HandleService
componentpublic 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
componentpublic 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 |
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. |
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.
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");
}
}
Again, in our app note framework, we add the following code:
HandlesModule
classpackage 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.
Be sure to modify this line in the MyKosApp
to instantiate the HandlesModule
:
MyKosApp
changepublic class MyKosApp extends SystemApplication<BaseAppConfig> {
public void initModule() {
module = new HandlesModule();
}
// . . .
}
This example found in HandlesModule
demonstrates how to retieve the list of all handles in the system:
// 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:
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
To see a particular object, try this:
http://localhost:8081/api/kos/handles/system.service:payment
This example found in HandlesModule
demonstrates how to grab an object with its handle:
// 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 |
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.