Developing the Memory Viewer context client
As usual, a memory viewer is first a normal OSGi component bundle. We described how to create components and bind them with services in the second and the third tutorials. As such, to develop a context client we need to specify three parts:
- Define its Manifest: The Manifest describes the properties of the bundle. As such it defines the imported and possibly exported packages and it also provides a pointer to the service descriptor.
- Define its service descriptor XML file: In the case of a context client, the service descriptor is simply used to define the needed IContextAccess service (which it was introduced in this tutorial).
- Define the code that implements the context client: The code of the context client is implemented in one or more classes. In principle, a context client implements the IContextListener interface which allows it to asynchronously access context data.
Manifest-Version: 1.0
Bundle-ManifestVersion: 2
Bundle-Name: Memory viewer client for the MUSIC Context System
Bundle-SymbolicName: MemoryViewer
Bundle-Version: 1.0.0
Bundle-ClassPath: .
Import-Package: org.istmusic.mw.context,
org.istmusic.mw.context.exceptions,
org.istmusic.mw.context.events,
org.istmusic.mw.context.ontologies,
org.istmusic.mw.context.model.api,
cy.ac.ucy.cs.osgi.tutorial7.memory_model
Service-Component: OSGI-INF/MemoryViewer.xml
As a typical OSGi component, its Manifest defines imported and exported packages and a pointer to the service descriptor XML file. In this case, the service descriptor is named "MemoryViewer" and is as follows:<?xml version="1.0"?>
<component name="MemoryViewer" immediate="true">
<implementation class="cy.ac.ucy.cs.osgi.tutorial9.MemoryViewer"/>
<!-- Reference to the context access service -->
<reference name="context.access"
interface="org.istmusic.mw.context.IContextAccess"
cardinality="1..1"
bind="setContextAccessService"
unbind="unsetContextAccessService"
policy="dynamic"/>
</component>
The most interesting part of this service descriptor is that it defines a reference to the context access service. By referring back to the details of dynamically binding (declarative) services, you can see that this definition will result to the automatic binding of this client to the IContextAccess service, which is provided by the context middleware. Furthermore, the "1..1" cardinality indicates that in order to activate the client, exactly one binding to a single provider of the context access service must be made. When bound (or unboun), a corresponding method is invoked.Finally, we are ready to present the code of the Memory Viewer context client:
package cy.ac.ucy.cs.osgi.tutorial9;
import org.istmusic.mw.context.IContextAccess;
import org.istmusic.mw.context.exceptions.ContextException;
import org.istmusic.mw.context.events.IContextListener;
import org.istmusic.mw.context.events.ContextChangedEvent;
import org.istmusic.mw.context.ontologies.DefaultMusicOntologyV0_1;
import org.istmusic.mw.context.model.api.IEntity;
import org.istmusic.mw.context.model.api.IScope;
import org.istmusic.mw.context.model.api.IContextElement;
import java.util.logging.Logger;
import cy.ac.ucy.cs.osgi.tutorial7.memory_model.MemoryContextElement;
public class MemoryViewer implements IContextListener
{
public static final IEntity ENTITY = DefaultMusicOntologyV0_1.ENTITY_DEVICE_THIS;
public static final IScope SCOPE = DefaultMusicOntologyV0_1.SCOPE_RESOURCE_MEMORY;
private final Logger logger = Logger.getLogger(MemoryViewer.class.getCanonicalName());
public MemoryViewer()
{
logger.info("MemoryViewer: constructed");
}
public void contextChanged(ContextChangedEvent event)
{
IContextElement [] contextElements = event.getContextDataset().getContextElements();
for(int i = 0; i < contextElements.length; i++)
{
IContextElement contextElement = contextElements[i];
MemoryContextElement memoryContextElement = (MemoryContextElement) contextElement;
logger.info("ContextViewer: Received -> " + memoryContextElement);
}
}
public void setContextAccessService(final IContextAccess contextAccess)
{
try
{
contextAccess.addContextListener(ENTITY, SCOPE, this);
}
catch (ContextException ce)
{
logger.throwing(MemoryViewer.class.getName(), "setContextAccessService", ce);
}
}
public void unsetContextAccessService(final IContextAccess contextAccess)
{
try
{
contextAccess.removeContextListener(ENTITY, SCOPE, this);
}
catch (ContextException ce)
{
logger.throwing(MemoryViewer.class.getName(), "unsetContextAccessService", ce);
}
}
}
The most interesting parts in this class are the handling of the binding (and unbinding) of the context access service (in bold) and the handling of the received events (also in bold). The "setContextAccess" method is used to register this context listener for notifications of events related to changes in the memory of this device. Symmetrically to it, the "unsetContextAccess" method is used to unregister from events of the same type.At this point, we should note that the context types are defined via the Ontology, which guarantees semantic consistency. For this purpose the entity and the scope are defined by referencing to the appropriate concepts in the ontology, which correspond to the memory resource of this device.
While registered with the context access service, the memory client receives every notification which corresponds to the registered context type. These notifications are received at the "contextChanged" method (which is defined in the IContextListener interface). In the Memory Viewer client, we simply read each context element received, cast it to the MemoryContextElement type (defined in Tutorial 7) and then simply print it out on the console. A real, context-aware application would of course the received context data in a more interesting way (e.g., to adapt its behavior).
Packaging and deploying the Memory Viewer context client
Following a similar approach to when developing OSGi bundles (and context plug-ins), we package the resulting code and the artifact documents (i.e., the Manifest and the service descriptor) in a single JAR file. An example of this JAR file, including a copy of the source code and the "build.xml" ANT configuration file that was used to create it, is available here.
When deployed and started, the memory viewer produces the following output:
osgi> install file:MemoryViewer.jar
Bundle id is 10
osgi> refresh 10
osgi> ss
Framework is launched.
id State Bundle
0 ACTIVE org.eclipse.osgi_3.4.2.R34x_v20080826-1230
1 ACTIVE org.eclipse.equinox.ds_1.0.0.v20080427-0830
2 ACTIVE org.eclipse.equinox.util_1.0.0.v20080414
3 ACTIVE org.eclipse.osgi.services_3.1.200.v20071203
4 ACTIVE RunnableProvider_1.0.0
5 ACTIVE RunnableConsumer_1.0.0
6 ACTIVE CLI_Client_1.0.0
7 ACTIVE music-context_0.2.1.0-SNAPSHOT
8 ACTIVE MemoryModel_1.0.0
9 ACTIVE MemorySensor_1.0.0
10 RESOLVED MemoryViewer_1.0.0
osgi> start 10
osgi> Jan 29, 2009 12:34:15 PM cy.ac.ucy.cs.osgi.tutorial9.MemoryViewer <init>
INFO: MemoryViewer: constructed
Jan 29, 2009 12:34:15 PM org.istmusic.mw.context.manager.ContextManager addTrigger
INFO: Adding trigger for context type: http://www.ist-music.eu/Ontology_v0_1.xml#concepts.entities.device|this/http://www.ist-music.eu/Ontology_v0_1.xml#concepts.scopes.resources.memory
Jan 29, 2009 12:34:15 PM cy.ac.ucy.cs.osgi.tutorial8.MemorySensor activate
INFO: c_activating: Memory plugin
Jan 29, 2009 12:34:16 PM cy.ac.ucy.cs.osgi.tutorial8.MemorySensor run
INFO: MemorySensor: firing event: (#concepts.entities.device|this, #concepts.scopes.resources.memory)->[availableMemory=2437856, memoryLoad=0.5291299940664558]
Jan 29, 2009 12:34:16 PM cy.ac.ucy.cs.osgi.tutorial9.MemoryViewer contextChanged
INFO: ContextViewer: Received -> (#concepts.entities.device|this, #concepts.scopes.resources.memory)->[availableMemory=2437856, memoryLoad=0.5291299940664558]
osgi> stop 10
Jan 29, 2009 12:34:29 PM cy.ac.ucy.cs.osgi.tutorial8.MemorySensor deactivate
INFO: c_deactivating: Memory plugin
osgi>
There are many interesting observations to be made here. First, when the memory viewer (bundle 10) is started, it causes a sequence of events:- First, the constructor of the memory viewer bundle is invoked.
- Second, the context access service is bound, and the memory viewer registers for memory context types, as it is indicated by the info log of the context middleware (which adds a trigger for asynchronous notification).
- Third, the context middleware realizes that the needed context type is offered by the installed memory context plug-in (bundle 9) and it activates it. This is indicated by memory sensor itself which logs the corresponding action.
- Next, the memory context plug-in (bundle 9) starts producing (i.e., firing) context change events encoding MemoryContextElements. These are communicated to the context middleware.
- Finally, the memory viewer (bundle 10) receives the corresponding events and prints out to the console the value of the received MemoryContextElement.
Finally, when you stop the memory viewer (bundle 10), it first unregisters from the context middleware, which in result causes it to deactivate the memory context plugin, returning it to the C_RESOLVED state. More details about the extended plug-in lifecycle were discussed in the previous tutorial and are also available in [1].
Homework 9.1: Develop a context client for the location context type and name it LocationViewer. The resulting component must register for location context types when active and unregister when not. The received context events must be printed out to the console. You can use the model defined in homework 7.1 and the LocationPlugin defined in homework 8.1.
Homework 9.2: Develop a graphical context client for the weather context type and name it WeatherViewer. The resulting component must register for location context types when active and unregister when not. The received context events must be displayed in a simple Graphical User Interface (GUI). You can use the model defined in homework 7.1 and the LocationPlugin defined in homework 8.1.
Hint: Use Java's swing to develop the GUI. In that case, do not forget to include all used packages (e.g., "javax.swing") into the imported packages definition of the bundle's Manifest. Also make the component visible when it is started and invisible when it is stopped, by implementing the "activate(ComponentContext)" and "deactivate(ComponentContext)" methods, as described in Tutorial 5.
[1]. Nearchos Paspallis, Romain Rouvoy, Paolo Barone, George A. Papadopoulos, Frank Eliassen, Alessandro Mamelli, A Pluggable and Reconfigurable Architecture for a Context-aware Enabling Middleware System, 10th International Symposium on Distributed Objects, Middleware, and Applications (DOA'08), Monterrey, Mexico, Nov 10 - 12, 2008, Springer Verlag LNCS 5331, pp. 553-570