So far, you haven’t implemented much functionality for the shell—you just created the activator to start it up and shut it down. In this section, we’ll show you how to implement the bulk of its functionality. You’ll use a simple command pattern to provide the executable actions to let you interactively install, start, stop, update, and uninstall bundles. You’ll even add a persistent history to keep track of previously executed commands.
A high-level understanding of the approach will be useful before you start. The main piece is the telnet binding, which listens to the configured port for connection requests. It spawns a new thread for each connecting client. The client sends command lines to its thread, where a command line consists of a command name and the arguments for the command. The thread parses the command line, selects the appropriate command, and invokes it with any specified arguments, as shown in figure 3.7 :
Commands process the arguments passed in to them. We won’t discuss the implementation of the telnet binding and the connection thread, but full source code is available in the companion code. We’ll dissect the command implementations to illustrate how to use Bundle and BundleContext.
Configuring bundles :
The shell needs two configuration properties : one for the port and one for the maximum number of concurrent connections. In traditional Java programming, you’d use the System.getProperty() method to retrieve them. When creating a bundle, you can use the BundleContext object to retrieve configuration properties instead. The main benefit of this approach is that it avoids the global aspect of System.getProperty() and allows properties per framework instance.
The OSGi specification doesn’t specify a user-oriented way to set bundle configuration properties, so different frameworks handle this differently; typically, they provide a configuration file where the properties are set. But the specification does require bundle-configuration properties to be backed by system properties, so you can still use system properties in a pinch. Retrieving bundle-configuration property values is standardized via theBundleContext.getProperty() method, as shown next :
This listing continues the activator implementation from listing 3.1; in the activator, you use these two methods to get configuration properties. Here, the methods use the BundleContext.getProperty() method to retrieve the properties B. This method looks in the framework properties to find the value of the specified property. If it can’t find the property, it searches the system properties, returning null if the property isn’t found. For the shell, you return default values if no configured value is found. The OSGi specification also defines some standard framework properties, shown in table 3.1. If you need to use these standard properties, you can use the constants for them defined in the org.osgi.framework.Constants class :
There you have it: your first real interaction with the OSGi framework. This is only a small part of the API that you can use in your bundles, but we’ll cover a lot of ground in the next section. There are other, more sophisticated ways to configure your bundle, but we won’t discuss them until chapter 9. Bundle properties are the simplest mechanism available and should only be used for properties that don’t change much. In this regard, they may not be the best choice for the shell, but it depends on what you want to achieve; for example, it makes it difficult to change the shell’s port dynamically. For now, we’ll keep things simple, so this is sufficient.
Deploying bundles :
Each bundle installed into the framework is represented by a Bundle object and can be identified by its bundle identifier, location, or symbolic name. For most of the shell commands you’ll implement, you’ll use the bundle identifier to retrieve a Bundle object, because the bundle identifier is nice and concise. Most of the commands accept a bundle identifier as a parameter, so let’s look at how you can use it and the bundle context to access Bundle objects associated with other bundles. As part of the design, you create an abstract BasicCommand class to define a shared method, getBundle(), to retrieve bundles by their identifier, as shown here :
- protected volatile BundleContext m_context;
- //...
- public Bundle getBundle(String id) {
- Bundle bundle = m_context.getBundle(Long.parseLong(id.trim()));
- if (bundle == null) {
- throw new IllegalArgumentException("No such bundle.");
- }
- return bundle;
- }
- INSTALL COMMAND
With this basic functionality in place, you can start the first command. The next listing shows the implementation of an install command, and figure 3.8 reminds you which portion of the bundle lifecycle is involved :
Figure 3.8 The install-related portion of the bundle lifecycle state diagram
You use BundleContext.installBundle() to install a bundle. In most framework implementations, the argument to installBundle() is conveniently interpreted as a URL in String form from which the bundle JAR file can be retrieved. Because the user enters the URL argument as a String, you can use it directly to install the bundle. If the install succeeds, then a new Bundle object corresponding to the newly installed bundle is returned. The bundle is uniquely identified by this URL, which is used as its location. This location value will also be used in the future to determine if the bundle is already installed. If a bundle is already associated with this location value, the Bundle object associated with the previously installed bundle is returned instead of installing it again. If the install operation is successful, the command outputs the installed bundle’s identifier.
The bundle context also provides an overloaded installBundle() method for installing a bundle from an input stream. We won’t show this method here, but the other form of installBundle() accepts a location and an open input stream. When you use this other form of the method, the location is used purely for identification, and the bundle JAR file is read from the passed-in input stream. The framework is responsible for closing the input stream.
- START COMMAND
Now you have a command to install bundles, so the next operation you’ll want to do is start bundles. The start command shown in listing 3.6 does just that (see figure 3.9) :
Figure 3.9 The start-related portion of the bundle lifecycle state diagram
Again, the implementation is pretty easy. You use the method from the base command class to get the Bundle object associated with the user-supplied identifier, and then you invoke Bundle.start() to start the bundle associated with the identifier.
The result of Bundle.start() depends on the current state of the associated bundle. If the bundle is INSTALLED, it transitions to ACTIVE via the RESOLVED andSTARTING states. If the bundle is UNINSTALLED, the method throws an IllegalStateException. If the bundle is either STARTING or STOPPING, start() blocks until the bundle enters either ACTIVE or RESOLVED. If the bundle is already ACTIVE, calling start() again has no effect. A bundle must be resolved before it can be started. You don’t need to explicitly resolve the bundle, because the specification requires the framework to implicitly resolve the bundle if it’s not already resolved. If the bundle’s dependencies can’t be resolved, start() throws a BundleException and the bundle can’t be used until its dependencies are satisfied. If this happens, you’ll typically install additional bundles to satisfy the missing dependencies and try to start the bundle again.
If the bundle has an activator, the framework invokes the BundleActivator.start() method when starting the bundle. Any exceptions thrown from the activator result in a failed attempt to start the bundle and an exception being thrown from Bundle.start(). One last case where an exception may result is if a bundle tries to start itself; the specification says attempts to do so should result in an IllegalStateException.
- STOP COMMAND
That’s it for starting bundles. Now we can look at stopping bundles, which is similar to starting them; see the next listing and figure 3.10 :
Figure 3.10 The stop-related portion of the bundle lifecycle state diagram
Like starting a bundle, stopping a bundle takes a simple call to Bundle.stop() on the Bundle object retrieved from the specified identifier. As before, you must be mindful of the bundle’s state. If it’s UNINSTALLED, an Illegal-StateException results. Either STARTING or STOPPING blocks until ACTIVE or RESOLVED is reached, respectively. In the ACTIVE state, the bundle transitions to RESOLVED via the STOPPING state. If the bundle has an activator and the activator’sstop() method throws an exception, a BundleException is thrown. Finally, a bundle isn’t supposed to change its own state; trying to do so may result in an IllegalStateException.
- UPDATE COMMAND
Let’s continue with the update command in the following listing (see figure 3.11).
Figure 3.11 The update-related portion of the bundle lifecycle state diagram
By now, you may have noticed the pattern we mentioned in the beginning. Most lifecycle operations are methods on the Bundle and BundleContext objects. The Bundle.update() method is no exception, as you can see. The update() method is available in two forms: one with no parameters (shown) and one taking an input stream (not shown). The update command uses the form without parameters here, which reads the updated bundle JAR file using the original location value as a source URL. If the bundle being updated is in the ACTIVE state, it needs to be stopped first, as required by the bundle lifecycle. You don’t need to do this explicitly, because the framework does it for you, but it’s still good to understand that this occurs because it impacts the application’s behavior. The update happens in either the RESOLVED or INSTALLED state and results in a new revision of the bundle in the INSTALLED state. If the bundle is in the UNINSTALLED state, an IllegalStateException is thrown. As in the stop command, a bundle shouldn’t try to update itself.
- UNINSTALL COMMAND
You can now wrap up the lifecycle operations by implementing the uninstall command, as shown next (see figure 3.12) :
Figure 3.12 The uninstall-related portion of the bundle lifecycle state diagram
To uninstall a bundle, you call the Bundle.uninstall() method after retrieving the Bundle object associated with the user-supplied bundle identifier. The framework stops the bundle, if necessary. If the bundle is already UNINSTALLED, an IllegalStateException is thrown. As with the other lifecycle operations, a bundle shouldn’t attempt to uninstall itself.
That’s it. You’ve created a telnet-based shell bundle that you can use in any OSGi framework. Most of the shell commands require the bundle identifier to perform their action, but how does the shell user know which identifier to use? You need some way to inspect the state of the framework’s installed bundle set. You’ll create a command for that next.
Inspecting framework state :
You need one more command to display information about the bundles currently installed in the framework. The next listing shows a simple implementation of a bundles command.
The implementation of this command is pretty easy too, because you only need to use BundleContext.getBundles() to get an array of all bundles currently installed in the framework. The rest of the implementation loops through the returned array and prints out information from each Bundle object. Here you print the bundle identifier, lifecycle state, name, location, and symbolic name for each bundle.
With this command in place, you have everything you need for the simple shell. You can install, start, stop, update, and uninstall bundles and list the currently installed bundles. That was fairly simple, wasn’t it? Think about the flexibility at your fingertips versus the amount of effort needed to create the shell. Now you can create applications as easily deployable configurations of bundles that you can manage and evolve as necessary over time.
Before you move back to the paint program, two final lifecycle concepts are worth exploring in order to fully appreciate the approach you’ll take to make the paint program dynamically extensible : persistence and events. We’ll describe them in the context of the shell example; but as you’ll see in the paint example in a couple of pages, they’re generally useful tools to have in mind when building OSGi applications.
Persisting bundle state :
As we mentioned when discussing bundle activators, the framework creates an instance of a bundle’s activator class and uses the same instance for starting and subsequently stopping the bundle. An activator instance is used only once by the framework to start and stop a bundle, after which it’s discarded. If the bundle is subsequently restarted, a new activator instance is created. Given this situation, how does a bundle persist state across stops and restarts? Stepping back even further, we mentioned how the framework saves installed bundles into a cache so they can be reloaded the next time the framework starts. How does a bundle persist state across framework sessions? There are several possibilities.
One possibility is to store the information outside the framework, such as in a database or a file, as shown in figure 3.13. The disadvantage of this
approach is that the state isn’t managed by the framework and may not be cleaned up when the bundle is uninstalled.
Another possibility is for a bundle to give its state to another bundle that isn’t being stopped; then, it can get the state back after it restarts, as shown in figure 3.14. This is a workable approach, and in some cases it makes the most sense.
Figure 3.14 Storing state with other bundles
For simplicity, it would be nice to be able to use files, but have them managed by the framework. Such a possibility exists. The framework maintains a private data area in the file system for each installed bundle. The BundleContext.getDataFile() method provides access to your bundle’s private data area. When using the private data area, you don’t need to worry about where it is on the file system because the framework takes care of that for you, as well as cleaning up in the event of your bundle being uninstalled (see figure 3.15). It may seem odd to not directly use files to store your data; but if you did, it would be impossible for your bundle to clean up during an uninstall. This is because a bundle isn’t notified when it’s uninstalled. Further, this method simplifies running with security enabled, because bundles can be granted permission to access their private area by the framework.
For the shell example, you want to use the private area to persistently save the command history. Here’s how the history command should work; it prints the commands issued via the shell in reverse order. Listing 3.11 shows how you use the bundle’s private storage area to save the command
history. The bundle activator’s start() and stop() methods also need to be modified to invoke these methods, but these changes aren’t shown here, so please refer to the companion code for complete implementation details.
You use BundleContext.getDataFile() to get a File object in the bundle’s private storage area B. The method takes a relative path as a String and returns a valid File object in the storage area. After you get the File object, you can use it normally to create the file, make a subdirectory, or do whatever you want. It’s possible for a framework to return null when a bundle requests a file. you need to handle this possibility. This can happen because the OSGi framework was designed to run on a variety of devices, some of which may not support a file system. For the shell, you ignore it if there’s no file system support, because the history command is noncritical functionality.
If you want to retrieve a File object for the root directory of your bundle’s storage area, you can call getDataFile() with an empty string. Your bundle is responsible for managing the content of its data area, but you don’t need to worry about cleaning up when it’s uninstalled, because the framework takes care of this.
You could finish the history command, but let’s try to make it a little more interesting by keeping track of what’s going on inside the framework. You can record not only the issued commands, but also the impact they have on the framework. The next section shows how you can achieve this using the framework’s eventnotification mechanism.
Listening for events :
The OSGi framework is a dynamic execution environment. To create bundles and, ultimately, applications that are flexible enough to not only cope with but also take advantage of this dynamism, you need to pay attention to execution-time changes. The lifecycle layer API provides access to a lot of information, but it isn’t easy to poll for changes; it’s much more convenient if you can be notified when changes occur. To make this possible, the OSGi framework supports two types of events: BundleEvents and FrameworkEvents. The former event type reports changes in the lifecycle of bundles, whereas the latter reports framework-related issues.
You can use the normal Java listener pattern (Observer pattern) in your bundles to receive these events. The BundleContext object has methods to registerBundleListener and FrameworkListener objects for receiving BundleEvent and FrameworkEvent notifications, respectively. The following listing shows how you implement the history command. You record all executed commands as well as the events they cause during execution.
You use an interceptor pattern to wrap the commands so you can record the issued commands. The wrapper also records any events in the history by implementing the BundleListener and FrameworkListener interfaces. You maintain a list of all issued commands and received events in the m_history member defined at (1). The history wrapper command forwards the command execution to the command (2) and stores it in the history list.
The wrapper implements the single FrameworkListener.frameworkEvent(). Here, you record the event information in the history list. The most important part of the event is its type. Framework events are of one of the following types :
The wrapper also implements the single BundleListener.bundleChanged() method. Here, you also record the event information in the history list. Bundle events have one of the following types :
You register the listeners using the bundle context as follows :
- private void addListener(BundleContext context,
- BundleListener bundleListener, FrameworkListener frameworkListener) {
- context.addBundleListener(bundleListener);
- context.addFrameworkListener(frameworkListener);
- }
For the most part, the framework delivers events asynchronously. It’s possible for framework implementations to deliver them synchronously, but typically they don’t because it complicates concurrency handling. Sometimes you need synchronous delivery because you need to perform an action as the event is happening, so to speak. This is possible for BundleEvents by registering a listener implementing the SynchronousBundleListener interface instead ofBundleListener. The two interfaces look the same, but the framework delivers events synchronously to SynchronousBundle-Listeners, meaning the listener is notified during the processing of the event.The following event types are only sent to SynchronousBundleListeners :
Synchronous bundle listeners are sometimes necessary (as you’ll see in the paint example in the next section), but should be used with caution. They can lead to concurrency issues if you try to do too much in the callback; as always, keep your callbacks as short and simple as possible and don’t call foreign code while holding a lock. In all other cases, the thread invoking the listener callback method is undefined. Events become much more important when you start to write more sophisticated bundles that take full advantage of the bundle lifecycle.
Bundle suicide :
We’ve mentioned it numerous times: a bundle isn’t supposed to change its own state. But what if a bundle wants to change its own state? Good question. This is one of the more complicated aspects of the lifecycle layer, and there are potentially negative issues involved.
The central issue is that if a bundle stops itself, it finds itself in a state it shouldn’t be in. Its BundleActivator.stop() method has been invoked, which means its bundle context is no longer valid. Additionally, the framework has cleaned up its book-keeping for the bundle and has released any framework facilities it was using, such as unregistering all of its event listeners. The situation is even worse if a bundle tries to uninstall itself, because the framework will likely release its class loader. In short, the bundle is in a hostile environment, and it may not be able to function properly.
Because its bundle context is no longer valid, a stopped bundle can no longer use the functionality provided by the framework. Most method calls on an invalid bundle context will throw IllegalStateExceptions. Even if the bundle’s class loader is released, this may not pose a serious issue if the bundle doesn’t need any new classes, because the class loader won’t be garbage collected until the bundle stops using it. But you’re not guaranteed to be able to load new classes if the bundle was uninstalled.
Depending on your bundle, you may run into other issues too. If your bundle creates and uses threads, it’s typically a good idea for it to wait for all of its threads to complete when its BundleActivator.stop() method is called. If the bundle tries to stop itself on its own thread, that same thread can end up in a cycle waiting for other sibling threads to complete. In the end, the thread waits forever. For example, the simple shell uses a thread to listen for telnet connections and then uses secondary threads to execute the commands issued on those connections. If one of the secondary threads attempts to stop the shell bundle itself, it ends up waiting in the shell bundle’s BundleActivator.stop() method for the connection thread to stop all of the secondary threads. Because the calling thread is one of the secondary threads, it’ll end up waiting forever for the connection thread to complete. You have to be careful of these types of situations, and they’re not always obvious.
Under normal circumstances, you shouldn’t try to stop, uninstall, or update your own bundle. OK—that should be enough disclaimers. Let’s look at a case where you may need to do it anyway. We’ll use the shell as an example, because it provides a means to update bundles, and it may need to update itself. What do you have to do to allow a user to update the shell bundle via the shell command line? You must do two things to be safe :
You need to do this to prevent yourself from waiting forever for the shell thread to return when you get stopped and to avoid the potential ugliness of the hostile environment in which the thread will find itself. The following listing shows the changes to the implementation of the stop command to accommodate this scenario :
You use the BundleContext.getBundle() method to get a reference to the bundle representation and compare it to the target bundle (1). When the target is the shell bundle, you need to stop it using a different thread. For this reason, you create and start a new thread of type SelfStopThread, which executes theBundle.stop() method (2). There’s one final point to note in this example: you change the behavior of stopping a bundle in this case from synchronous to asynchronous. Ultimately, this shouldn’t matter much, because the bundle will be stopped anyway.
You should also modify the implementation of the update and uninstall commands the same way. Using the shell to stop the framework (the system bundle) also requires special consideration. Why? Because stopping the system bundle causes the framework to stop, which stops every other bundle. This means you’ll stop your bundle indirectly, so you should make sure you’re using a new thread.
Supplement :
This message was edited 29 times. Last update was at 15/07/2011 17:50:37
沒有留言:
張貼留言