Skip to content

Advanced_Resource_Access_Resource_Patterns_

David Nestle edited this page Feb 27, 2020 · 1 revision

Advanced Resource Access (Resource Patterns)

The advanced access is an alternative way to access and create OGEMA resources, to the basic methods described in Working with OGEMA Resources. The name should not be misunderstood, however; the purpose of the advanced access is to simplify the handling of resources for the developer, in particular to spare him or her from the intricacies of having to keep track of value changes and structure changes in the Resource tree by means of different types of listeners, such as ResourceDemandListeners, ResourceStructureListeners and ResourceValueListeners. In addition, the central concept of ResourcePatterns allows a very clear way to declare which Resources are used by a given application.

Content

Resource Patterns


ResourcePatterns can be used to create particular ensembles of Resources, and to register listeners for them. In its most basic incarnation, a ResourcePattern consists of a primary Resource type, the 'demanded model', which has to be specified as a Generics parameter, and additional subresources. An example could look like this:

Basic ResourcePattern example

public class CoolspacePattern extends ResourcePattern<CoolingDevice> {

    public TemperatureResource temperature = model.temperatureSensor().reading();

    private FloatResource m_maxTemp =  model.temperatureSensor().settings().controlLimits().upperLimit();

    private FloatResource m_minTemp = model.temperatureSensor().settings().controlLimits().lowerLimit();
    
    // Default constructor, invoked by the framework; must be public
    public CoolspacePattern(Resource device) {
        super(device);
    }
}

The model variable represents the demanded model here, of type CoolingDevice. If we were to create resources based on this pattern, a toplevel CoolingDevice would be created, as well as the three subresources. On the other hand, if we registered a pattern demand for this pattern, we would receive a patternAvailable() callback for all active resources of type CoolingDevice, which possess the indicated subresources (they must be active as well). A CoolingDevice which only has a temperature sensor, but no control limits would not trigger a callback.

Resource Pattern Demand


We can use PatternListeners to get informed about all appearing and disappearing Resource patterns that match our registered pattern class. A PatternListener should be thought of as an enhanced version of a ResourceDemandListener; it enables us to specify further conditions on a resource in order for a callback to be triggered, such as the existence of certain subresources. Theses conditions are defined by means of the pattern fields.

A PatternListener must implement the two methods patternAvailable() and patternUnavailable():

PatternListener example

public class CoolspaceListener implements PatternListener<CoolspacePattern> {
        
    private List<CoolspacePattern> availablePatterns = new LinkedList<CoolspacePattern>();
    @Override
    public void patternAvailable(CoolspacePattern pattern) {
        logger.info("New CoolspacePattern available " + pattern.model.getLocation());
        if (!availablePatterns.contains(pattern)) availablePatterns.add(pattern);
    }
    @Override
    public void patternUnavailable(CoolspacePattern pattern) {
        logger.info("CoolspacePattern unavailable " + pattern.model.getLocation());
        availablePatterns.remove(pattern);        
    }
}

Register your listener with the addPatternDemand() method of the ResourcePatternAccess (see Working with OGEMA Resources#OGEMAservicesforResourceaccess), as in the following example:

CoolspaceListener coolspaceListener = new CoolspaceListener();
rpa.addPatternDemand(CoolspacePattern.class, coolspaceListener, AccessPriority.PRIO_DEVICESPECIFIC);

Here rpa is the ResourcePatternAccess object obtained from the ApplicationManager. The third parameter specifies the AccessPriority for the resources requested by the pattern demand (see Working with OGEMA Resources#ReadingandWritingResourcevalues). Remember todeactivate your demand in the Application.stop() method.

The accept() method

Often there are additional constraints on the Resource patterns that need to be satisfied before we want to receive a callback. For instance, we might ask that the value of a subresource isControllable() of type BooleanResource is true. Much more complex filters may occur as well. For this purpose, ResourcePatterns can define an individual filter by overriding the accept() method of the generic ResourcePattern class, which has a boolean return value. A pattern only triggers a callback if it is complete, and the accept() method return true. So, let's add one to our example from above:

ResourcePattern with accept method

public class CoolspacePattern extends ResourcePattern<CoolingDevice> {

    public TemperatureResource temperature = model.temperatureSensor().reading();

    private FloatResource m_maxTemp = model.temperatureSensor().settings().controlLimits().upperLimit();

    private FloatResource m_minTemp = model.temperatureSensor().settings().controlLimits().lowerLimit();
    
    // Default constructor, invoked by the framework; must be public
    public CoolspacePattern(Resource device) {
        super(device);
    }

    public boolean accept() {
        if (m_maxTemp.getCelsius() > 20) || m_minTemp.getCelsius() < 0) {  // we only want to manage ordinary fridges
            return false;
        }
        return true;
    }

}

Icon Note that in this example the framework will only check once whether the accept() condition is satisfied, as soon as the pattern is completed. We assume that the control limits of a cooling device usually do not change. In other cases this one-time check will not be sufficient, and we need to ensure that the condition is re-checked upon value changes of the involved resources.

How can we tell the framework to re-check the accept()-condition upon value changes of a resource? Simply add an annotation to the respective resource in the pattern:

@ValueChangedListener(activate = true)
private FloatResource m_maxTemp = model.temperatureSensor().settings().controlLimits().upperLimit();

It may also happen that the custom filter depends on optional subresources, i.e. subresources which must not necessarily exist for the pattern to be considered complete, but which influence the filter. We can declare such subresources as optional elements in the pattern:

@Existence(required = CreateMode.OPTIONAL)
private BooleanResource controllable = model.onOffSwitch().controllable();

This will trigger repeated checks of the accept()-Method whenever the controllable() subresource is activated or deactivated (or created/deleted). We could also add a ValueChangedListener to this subresource, in order to trigger execution of the filter method on value changes of the controllable resource.

Create and activate Patterns


Patterns can be used to create Resources (ResourcePatternAccess.createResource()):

CoolspacePattern newPattern = rpa.createResource("myPatternBaseResource", CoolspacePattern.class);

in which case a toplevel Resource is created of the type specified as demanded model in the pattern, as well as the subresources specified in the pattern. Optional entries are not created, and the accept()-Method is not evaluated.

Note that just like Resource#create, the the create-Method for patterns does not activate the resources. In order to activate all resource-fields of a pattern, call

rpa.activatePattern(newPattern);

Pattern listeners only receive callbacks for active patterns (i.e. the model resource and all non-optional fields must be active).

PatternChangeListeners


(available from version 2.1.0 onwards)

A PatternChangeListener can be registered for individual pattern matches (but also pattern instances that aren't "matches", i.e. which do not satisfy all of the pattern conditions), and reports changes in any of the pattern's fields which are annotated by a @ChangeListener annotation. The annotation can be configured individually to report value and/or structure changes.Just like the PatternListener should be thought of as an enhanced version of the ResourceDemandListener, a PatternChangeListener is an enhanced version of both ResourceStructureListener and ResourceValueListener. An  additional aspect of the PatternChangeListener is, that it reports all modifications made in a single transaction (see Resource Transactions) in a single callback, therefore reducing the number of callbacks that need to be processed. Its callback method 

void patternChanged(P instance, List<CompoundResourceEvent<?>> changes);

hence receives a list of events.

An example pattern class for use with a PatternChangedListener could look like this:

Example: Pattern with ChangeListener annotation  Expand source

 public class ThermostatPattern extends ResourcePattern<Thermostat> {

    public ThermostatPattern(Resource match) {
        super(match);
    }
    
    public final TemperatureSensor tempSens = model.temperatureSensor();
    
    // by default, the value listener is activated
    @ChangeListener
    public final TemperatureResource reading = tempSens.reading();
    
    // here we are reported about structure changes (the Room class does not contain a value)
    @ChangeListener(structureListener=true)
    @Existence(required=CreateMode.OPTIONAL)
    public final Room room = model.location().room(); 

    // both structual and vlaue changes are reported
    @ChangeListener(structureListener=true,valueListener=true)
    @Existence(required=CreateMode.OPTIONAL)
    public final StringResource name = model.name();
}

The corresponding listener and its registration could look like this:

Example: PatternChangeListener  Expand source

PatternChangeListener<ThermostatPattern> listener = new PatternChangeListener<ThermostatPattern> {

    @Override
    public void patternChanged(ChangeListenerPattern instance, List<CompoundResourceEvent<?>> changes) {
        System.out.println(" Pattern changed callback, " + instance);
        for (CompoundResourceEvent<?> event: changes) {
            System.out.println("  Event of type " + event.getType() + " on resource " + event.getChangedResource());
        }
    }

}
ThermostatPattern instance = applicationManager.getResourcePatternAccess().createResource("testPattern", ThermostatPattern.class);
applicationManager.getResourcePatternAccess().addPatternChangeListener(instance, listener, ThermostatPattern.class);

Another typical usage would be to register a PatternListener for patterns of a specific type, and to add a PatternChangeListener for all matches found in the PatternListener's patternAvailable() method.

Summary: Annotations


The following annotations can be used on fields of a ResourcePattern:

Annotation Target Values Effect

@Existence(required=?);

PatternListener
  • CreateMode.MUST_EXIST (default)
  • CreateMode.OPTIONAL
  • PatternDemand: framework does not check optional fields; patternAvailable() callbacks are issued even if optional elements are missing; main raison d'être of these fields is to trigger renewed execution of the accept()-Method.
  • Create Pattern: optional fields are not created.
@Access(mode=?, required=?) PatternListener
  • PatternDemand: if required==true, only patterns granting at least the specified AccessMode for the respective resource are considered complete. Furthermore, the specified AccessMode is registered for the resource.
  • Create Pattern: the specified AccessMode is registered for the resource (question)
@ValueChangedListener(activate=?) PatternListener
  • true, false (default)
  • PatternDemand: once the pattern structure is complete, renewed execution of the access() method is triggered by value changes of the annotated resource. If access() returns false on a previously completed pattern, a patternUnavailable() callback is triggered for the registered listener(s), if it returns true on a previously incomplete pattern, resourceAvailable() is called.
  • Create Pattern: this annotation has no effect on the creation of patterns.

@ChangeListener(
          structureListener = ?,
          valueListener = ?,
          callOnEveryUpdate= ?)

 

PatternChangeListener
  • structureListener: boolean (default false)
  • valueListener: boolean (default true)
  • callOnEveryUpdate: boolean (default true), only relevant if valueListener is true
  • Activate reporting of changes to a PatternChangeListener. Either structur changes (see ResourceStructureListener), value changes (see ResourceValueListener), or both.

 

Debugging Pattern demands (>= v2.0.5)


If you have registered a pattern demand in your application, you can display the full matches and the incomplete ones (matching model resource, but some other condition not satisfied) in the OGEMA console, through the command

apps -l <APP_NAME>

Furthermore, the demokit comes with a graphical tool that displays the same information (pattern-debugger).

Related Pages


Clone this wiki locally