Developing the CLI Client
In this example, we leverage a mechanism provided by OSGi which allows interacting with the framework via commands at the console at runtime. To achieve this, we use OSGi's CommandProvider mechanism. The latter, is implemented as a service which is exported by a component that wishes to interact with the framework via CLI, and which is consumed by the framework itself.
The bundle structure is similar to the one used in the previous two tutorials, and is omitted for brevity. The service descriptor on the other hand, has the distinctive that it both provides a service (the "CommandProvider") and it references one (the "java.lang.Runnable"):
<?xml version="1.0"?>
<component name="CLI_Client">
<implementation class="cy.ac.ucy.cs.osgi.tutorial4.CLI_Client"/>
<service>
<provide interface="org.eclipse.osgi.framework.console.CommandProvider"/>
</service>
<reference name="runnable_service"
interface="java.lang.Runnable"
bind="setRunnableService"
unbind="unsetRunnableService"
cardinality="0..1"
policy="dynamic"/>
</component>
It is worth noting that the reference element in this XML descriptor is identical to the one used in the "ServiceConsumer" described in the previous tutorial. On the other hand, the implementation class is of course adjusted to the new class, and a service element is added to define that this component implements the "CommandProvider" service, and thus is capable of interacting the CLI in the console.Proceeding to the source code of the CLI_Client component, again you can observe some similarities to the code of the "RunnableConsumer", i.e., the default constructor and the standard setter methods for the "java.lang.Runnable" service :
package cy.ac.ucy.cs.osgi.tutorial4;
import org.eclipse.osgi.framework.console.CommandProvider;
import org.eclipse.osgi.framework.console.CommandInterpreter;
public class CLI_Client implements CommandProvider
{
public CLI_Client()
{
super();
}
public String getHelp()
{
return "\ttest - Tests interaction with the Runnable service";
}
private Runnable runnableService = null;
public void setRunnableService(Runnable runnableService)
{
this.runnableService = runnableService;
}
public void unsetRunnableService(Runnable runnableService)
{
this.runnableService = null;
}
public void _test(CommandInterpreter ci)
{
if(runnableService == null)
{
System.out.println("Testing failed: no Runnable service provider available");
}
else
{
System.out.println("Invoking Runnable service ...");
runnableService.run();
System.out.println("Done!");
}
}
}
The most important observation in this code is that the implementing class in not a POJO anymore (compared to the previous two tutorials). Rather, the class implements the "CommandProvider" interface and even imports two classes:- org.eclipse.osgi.framework.console.CommandProvider
- org.eclipse.osgi.framework.console.CommandInterpreter
Finally, the bundle's MANIFEST is similar to those defined in the previous tutorials, but it introduces the new "Import-Package" parameter type:
Manifest-Version: 1.0
Bundle-ManifestVersion: 2
Bundle-Name: Tutorial 4: CLI_Client
Bundle-SymbolicName: CLI_Client
Bundle-Version: 1.0.0
Import-Package: org.eclipse.osgi.framework.console
Service-Component: OSGI-INF/CLI_Client.xml
This new parameter type specifies packages that this bundle needs. A similar parameter type, namely "Export-Package", defines packages that are exported by the bundle. At runtime, OSGi evaluates the needs of each bundle against the offered packages, and it resolves only those bundles of which their needed packages are offered by some other, installed bundles. This, in principle, provides a powerful encapsulation mechanism (cf. information hiding), which improves that of plain Java.Overall, with the new additions we achieve to introduce new functionality to the CLI Client (compared to the "RunnableProvider" of the previous tutorial), in terms of the "test" method that can be invoked in the console. We illustrate this new functionality by deploying, starting and testing the CLI Client.
Deploying and activating the CLI_Client
First, make sure that you have followed the steps described in the previous tutorials to install and activate the "RunnableProvider" and the "RunnableConsumer" (if some components are not started, start them with the "start ID#" command).
Then, proceed to install and activate the CLI_Client. Providing the "bundle 6" command (where 6 is the ID assigned to the CLI Client by the framework), we observe that the component registers the "CommandProvider" service (which is used by the framework for Command Line Interaction as described already) and it uses the "java.lang.Runnable" service, provided by the "RunnableProvider". Furthermore, we note that the bundle imports a package related to OSGi.
osgi> install file:CLI_Client.jar
Bundle id is 6
osgi> start 6
osgi> bundle 6
file:CLI_Client.jar [6]
Id=6, Status=ACTIVE Data Root=C:\eclipse\configuration\org.eclipse.osgi\bundles\6\data
Registered Services
{org.eclipse.osgi.framework.console.CommandProvider}={component.name=CLI_Client, component.id=10, service.id=30}
Services in use:
{java.lang.Runnable}={component.name=RunnableProvider, component.id=9, service.id=29}
No exported packages
Imported packages
org.eclipse.osgi.framework.console; version="1.0.0"<System Bundle [0]>
No fragment bundles
Named class space
CLI_Client; bundle-version="1.0.0"[provided]
No required bundles
osgi> test
Invoking Runnable service ...
RunnableProvider says hi!
Done!
osgi> stop 4
osgi> test
Testing failed: no Runnable service provider available
Notably, when we invoke the custom "test" command, the framework invokes the corresponding method in the "CLI_Client" component, which prints out a message ("Invoking Runnable service ..."), then invokes the service (which results to the output of "RunnableProvider says hi!", as it was specified in the second tutorial) and finally it prints out a third line ("Done!").This means that the invocation of the service was successful, and resulted to the invocation of the "run()" method all the way from the client side, to the service side. Just to make sure that the dynamic binding works, we can try to stop the "ServiceProvider" and run the "test" command again. The former causes the unbinding of the "java.lang.Runnable" service from the clients, which results to setting it to null and thus the print-out of the "Testing failed: no Runnable service provider available" message.
Finally, to show how a service provider can serve more than one clients simultaneously, we can try starting the service back again (via the "start 4" command), and then inspect the service:
osgi> start 4
osgi> services (objectClass=java.lang*)
{java.lang.Runnable}={component.name=RunnableProvider, component.id=12, service.id=32}
Registered by bundle: file:RunnableProvider.jar [4]
Bundles using service:
file:RunnableConsumer.jar [5]
file:CLI_Client.jar [6]
In this case, we can observe that the service is registered by the "RunnableProvider" component, and is assigned an ID (32 in this case, might be different in your case). Notably, there are two bundles using the service: the "RunnableConsumer" defined in the previous tutorial and the "CLI_Client" defined in this one.Homework 4.1: Read section 2.10 (Interacting with the Framework) in the second chapter of Neil Bartlett's book.
Homework 4.2: Write a bundle that periodically checks the contents of a directory in the filesystem. Whenever a new file with the extension ".jar" appears in that directory, print out an appropriate message.
No comments:
Post a Comment