By convention in Brooklyn the following words have a particular meaning:
The following constructs are often used for Java entities:
start()
) and on tear down (stop()
).Configuration keys are typically defined as static named fields on the Entity interface. These define the configuration values that can be passed to the entity during construction. For example:
{% highlight java %} public static final ConfigKey ROOT_WAR = new ConfigKeys.newStringConfigKey( “wars.root”, “WAR file to deploy as the ROOT, as URL (supporting file: and classpath: prefixes)”); {% endhighlight %}
One can optionally define a @SetFromFlag("war")
. This defines a short-hand for configuring the entity. However, it should be used with caution - when using configuration set on a parent entity (and thus inherited), the @SetFromFlag
short-form names are not checked. The long form defined in the constructor should be meaningful and sufficient. The usage of @SetFromFlag
is therefore discouraged.
The type AttributeSensorAndConfigKey<?>
can be used to indicate that a config key should be resolved, and its value set as a sensor on the entity (when ConfigToAttributes.apply(entity)
is called).
A special case of this is PortAttributeSensorAndConfigKey
. This is resolved to find an available port (by querying the target location). For example, the value 8081+
means that then next available port starting from 8081 will be used.
Sensors are typically defined as static named fields on the Entity interface. These define the events published by the entity, which interested parties can subscribe to. For example:
{% highlight java %} AttributeSensor MANAGEMENT_URL = Sensors.newStringSensor( “crate.managementUri”, “The address at which the Crate server listens”); {% endhighlight %}
Effectors are the operations that an entity supports. There are multiple ways that an entity can be defined. Examples of each are given below.
A method on the entity interface can be annotated to indicate it is an effector, and to provide metadata about the effector and its parameters.
{% highlight java %} @org.apache.brooklyn.core.annotation.Effector(description=“Retrieve a Gist”) public String getGist(@EffectorParam(name=“id”, description=“Gist id”) String id); {% endhighlight %}
A static field can be defined on the entity to define an effector, giving metadata about that effector.
{% highlight java %} public static final Effector EXECUTE_SCRIPT = Effectors.effector(String.class, “executeScript”) .description(“invokes a script”) .parameter(ExecuteScriptEffectorBody.SCRIPT) .impl(new ExecuteScriptEffectorBody()) .build(); {% endhighlight %}
In this example, the implementation of the effector is an instance of ExecuteScriptEffectorBody
. This implements EffectorBody
. It will be invoked whenever the effector is called.
An effector can be added to an entity dynamically - either as part of the entity's init()
or as separate initialization code. This allows the implementation of the effector to be shared amongst multiple entities, without sub-classing. For example:
{% highlight java %} Effector GET_GIST = Effectors.effector(Void.class, “createGist”) .description(“Create a Gist”) .parameter(String.class, “id”, “Gist id”) .buildAbstract();
public static void CreateGistEffectorBody implements EffectorBody() { @Override public Void call(ConfigBag parameters) { // impl return null; } }
@Override public void init() { getMutableEntityType().addEffector(CREATE_GIST, new CreateGistEffectorBody()); } {% endhighlight %}
There are several ways to invoke an effector programmatically:
Where there is an annotated method, simply call the method on the interface.
Call the invoke
method on the entity, using the static effector declaration. For example:entity.invoke(CREATE_GIST, ImmutableMap.of("id", id));
.
Call the utility method org.apache.brooklyn.core.entity.Entities.invokeEffector
. For example:Entities.invokeEffector(this, targetEntity, CREATE_GIST, ImmutableMap.of("id", id));
.
When an effector is invoked, the call is intercepted to wrap it in a task. In this way, the effector invocation is tracked - it is shown in the Activity view.
When invoke
or invokeEffector
is used, the call returns a Task
object (which extends Future
). This allows the caller to understand progress and errors on the task, as well as calling task.get()
to retrieve the return value. Be aware that task.get()
is a blocking function that will wait until a value is available before returning.
Warning: the task API may be changed in a future release. However, backwards compatibility will be maintained where possible.
When implementing entities and policies, all work done within Brooklyn is executed as Tasks. This makes it trackable and visible to administrators. For the activity list to show a break-down of an effector's work (in real-time, and also after completion), tasks and sub-tasks must be created.
In common situations, tasks are implicitly created and executed. For example, when implementing an effector using the @Effector
annotation on a method, the method invocation is automatically wrapped as a task. Similarly, when a subscription is passed an event (e.g. when using SensorEventListener.onEvent(SensorEvent<T> event)
, that call is done inside a task.
Within a task, it is possible to create and execute sub-tasks. A common way to do this is to use DynamicTasks.queue
. If called from within a a “task queuing context” (e.g. from inside an effector implementation), it will add the task to be executed. By default, the outer task will not be marked as done until its queued sub-tasks are complete.
When creating tasks, the TaskBuilder
can be used to create simple tasks or to create compound tasks whose sub-tasks are to be executed either sequentially or in parallel. For example:
{% highlight java %} TaskBuilder.builder() .displayName(“stdout-example”) .body(new Callable() { public Integer call() { System.out.println(“example”; } }) .build(); {% endhighlight %}
There are also builder and factory utilities for common types of operation, such as executing SSH commands using SshTasks
.
A lower level way to submit tasks within an entity is to call getExecutionContext().submit(...)
. This automatically tags the task to indicate that its context is the given entity.
An even lower level way to execute tasks (to be ignored except for power-users) is to go straight
to the getManagementContext().getExecutionManager().submit(...)
. This is similar to the standard Java Executor
, but also supports more metadata about tasks such as descriptions and tags. It also supports querying for tasks. There is also support for submitting ScheduledTask
instances which run periodically.
The Tasks
and BrooklynTaskTags
classes supply a number of conveniences including builders to make working with tasks easier.
Entities, locations, policies and enrichers can subscribe to events. These events could be attribute-change events from other entities, or other events explicitly published by the entities.
A subscription is created by calling subscriptions().subscribe(entity, sensorType, sensorEventListener)
. The sensorEventListener
will be called with the event whenever the given entity emits a sensor of the given type. If null
is used for either the entity or sensor type, this is treated as a wildcard.
It is very common for a policy or enricher to subscribe to events, to kick off actions or to publish other aggregated attributes or events.