Dynamically extending the paint program :
As you’ll recall from the last chapter, you first converted a nonmodular version of the paint program into a modular one using an interfacebased programming approach for the architecture. This is great because you can reuse the resulting bundles with minimal extra work. The bundles containing the shape implementations don’t need to change, except for some additional metadata in their manifest. You just need to modify the paint program to make it possible for shapes to be added and removed at execution time.
The approach you’ll take is a well-known pattern in the OSGi world, called the extender pattern. The main idea behind the extender pattern is to model dynamic extensibility on the lifecycle events (installing, resolving, starting, stopping, and so on) of other bundles. Typically, some bundle in the application acts as the extender: it listens for bundles being started and/or stopped. When a bundle is started, the extender probes it to see if it’s an extension bundle. The extender looks in the bundle’s manifest (using Bundle.getHeaders()) or the bundle’s content (using Bundle.getEntry()) for specific metadata it recognizes. If the bundle does contain an extension, the extension is described by the metadata. The extender reads the metadata and performs the necessary tasks on behalf of the extension bundle to integrate it into the application. The extender also listens for extension bundles to be stopped, in which case it removes the associated extensions from the application. That’s the general description of the extender pattern, which is shown in figure 3.16. Let’s look at how you’ll use it in the paint program.
You’ll treat the shape implementations as extensions. The extension metadata will be contained in the bundle manifest and will describe which class implements the shape contained in the shape bundle. The extender will use this information to load the shape class from the bundle, instantiate it, and inject it into the application when an extension bundle is activated. If a shape bundle is stopped, the extender will remove it from the application. Figure 3.17 illustrates this usage scenario :
Figure 3.17 Paint program as an implementation of the extender pattern
Let’s dive in and start converting the application. The first thing you need to do is define the extension metadata for shape bundles to describe their shape implementation. In the following snippet, you add a couple of constants to the SimpleShape interface for extension metadata property names; it’s not strictly necessary to add these, but it’s good programming practice to use constants :
The constants indicate the name of the shape, the bundle resource file for the shape’s icon, and the bundle class name for the shape’s class. The draw()method draws the shape on the canvas. From the constants, it’s fairly straightforward to see how you’ll describe a specific shape implementation. You only need to know the name, an icon, and the class implementing the shape. As an example, for the circle implementation you add the following entries to its bundle manifest :
The name is just a string, and the icon and class refer to a resource file and a class inside the bundle JAR file, respectively. You add similar metadata to the manifests of all shape implementation bundles, which converts them all to extensions. Next, you need to tweak the architecture of the paint program to make it cope with dynamic addition and removal of shapes. Figure 3.18 captures the updated design.
Figure 3.18 Dynamic paint program class relationships
Comparing the new design to the original, you add two new classes: ShapeTracker and DefaultShape. They help you dynamically adapt the paint frame to deal with SimpleShape implementations dynamically appearing and disappearing. In a nutshell, the ShapeTracker is used to track when extension bundles start or stop, in which case it adds or removes DefaultShapes to/from the PaintFrame, respectively.
The concrete implementation of the ShapeTracker is a subclass of another class, called BundleTracker. The latter class is a generic class for tracking when bundles are started or stopped. Because BundleTracker is somewhat long, we’ll divide it across multiple listings; the first part is shown next.
The bundle tracker is constructed with a BundleContext object, which is used to listen for bundle lifecycle events. The tracker uses aSynchronousBundleListener to listen to events because a regular BundleListener doesn’t get notified when a bundle enters the STOPPING state, only STOPPED. You need to react on the STOPPING event instead of the STOPPED event because it’s still possible to use the stopping bundle, which hasn’t been stopped yet; a potential subclass might need to do this if it needed to access the stopping bundle’s BundleContext object. The bundle listener’s single method (1) makes sure the tracker is tracking bundles (2). If so, for started events, it adds the associated bundle to its bundle list (3) and invokes the abstractaddedBundle() method. Likewise, for stopping events, it removes the bundle from its bundle list and invokes the abstract removedBundle() method.
The following listing shows the next portion of the BundleTracker :
To start a BundleTracker instance tracking bundles, you must invoke its open() method. This methods registers a bundle event listener and processes any existing ACTIVE bundles by adding them to its bundle list and invoking the abstract addedBundle() method. The getBundles() method provides access to the current list of active bundles being tracked. Because BundleTracker is abstract, subclasses must provide implementations of addedBundle() andremovedBundle() to perform custom processing of added and removed bundles, respectively.
The last portion of the BundleTracker is as follows :
Calling BundleTracker.close() stops it from tracking bundles. This removes its bundle listener, removes each currently tracked bundle from its bundle list, and invokes the abstract removedBundle() method.
Now that you know how the BundleTracker works, let’s return to its subclass, Shape-Tracker. The heart of this subclass is the processBundle() method shown next, which processes added and removed bundles.
ShapeTracker overrides BundleTracker’s addedBundle() and removedBundle() abstract methods to invoke processBundle() in either case. You determine whether the bundle is an extension by probing its manifest for the Extension-Name property B. Any bundle without this property in its manifest is ignored. If the bundle being added contains a shape, the code grabs the metadata from the bundle’s manifest headers and adds the shape to the paint frame wrapped as a DefaultShape (2). For the icon metadata, you use Bundle.getResource() to load it. If the bundle being removed contains a shape, you remove the shape from the paint frame (3).
DefaultShape, shown in listing 3.18, serves two purposes. It implements the SimpleShape interface and is responsible for lazily creating the shape implementation using the Extension-Class metadata. It also serves as a placeholder for the shape if and when the shape is removed from the application. You didn’t have to deal with this situation in the original paint program, but now shape implementations can appear or disappear at any time when bundles are installed, started, stopped, and uninstalled. In such situations, the DefaultShape draws a placeholder icon on the paint canvas for any departed shape implementations.
In summary, when the paint application is started, its activator creates and opens a ShapeTracker. This tracks STARTED and STOPPED bundle events, interrogating the associated bundle for extension metadata. For every started extension bundle, it adds a new DefaultShape for the bundle to the paint frame, which creates the shape implementation, if needed, using the extension metadata. When the bundle stops, the ShapeTracker removes the shape from the paint frame. When a drawn shape is no longer available, the DefaultShape is used to draw a placeholder shape on the canvas instead. If the departed shape reappears, the placeholder is removed and the real shape is drawn on the canvas again.
Now you have a dynamically extensible paint program, as demonstrated in Part1. Although we didn’t show the activator for the paint program, it’s reasonably simple and only creates the framework and shape tracker on start and disposes of them on stop. Overall, this is a good example of how easy it is to make a modularized application take advantage of the lifecycle layer to make it dynamically extensible. What you’re still missing at this point is a discussion about how the lifecycle and module layers interact with each other, which we’ll get into next.
Lifecycle and modularity :
A two-way relationship exists between OSGi’s lifecycle and module layers. The lifecycle layer manages which bundles are installed into the framework, which obviously impacts how the module layer resolves dependencies among bundles. The module layer uses the metadata in bundles to make sure all their dependencies are satisfied before they can be used. This symbiotic relationship creates a chicken-and-egg situation when you want to use your bundles; to use a bundle you have to install it, but to install a bundle you must have a bundle context, which are only given to bundles. This close relationship is also obvious in how the framework resolves bundle dependencies, especially when bundles are dynamically installed and/or removed. Let’s explore this relationship by first looking into bundle dependency resolution.
Resolving bundles :
The act of resolving a bundle happens at the discretion of the framework, as long as it happens before any classes are loaded from the bundle. Often, when resolving a given bundle, the framework ends up resolving another bundle to satisfy a dependency of the original bundle. This can lead to cascading dependency resolution, because in order for the framework to use a bundle to satisfy the requirements of another bundle, the satisfying bundle too must be resolved, and so on. Because the framework resolves dependencies when needed, it’s possible to mostly ignore transitioning bundles to the RESOLVEDstate; you can start a bundle and know the framework will resolve it before starting it, if possible. This is great compared to the standard Java way, where you can run into missing dependencies at any point during the lifetime of your application.
But what if you want to make sure a given bundle resolves correctly? For example, maybe you want to know in advance whether an installed bundle can be started. In this case, there’s a way to ask the framework to resolve the bundle directly, but it’s not a method on Bundle like most other lifecycle operations. Instead, you use the Package Admin Service. The Package Admin Service is represented as an interface and is shown here :
You can explicitly resolve a bundle with the resolveBundles() method, which takes an array of bundles and returns a Boolean flag indicating whether the bundles could be resolved. The Package Admin Service can do a bit more than resolving bundles, and it’s a fairly important part of the framework; it also supports the following operations, among others :
* Determines which bundle owns a particular class
* Introspects how the framework resolves bundle dependencies
* Refreshes the dependency resolution for bundles
The most important feature of the Package Admin Service isn’t the ability to resolve bundles or introspect dependencies; it’s the ability to refresh bundle dependencies, which is another tool needed for managing bundles. But before we get into the details of refreshing bundles, let’s finish the discussion of explicitly resolving bundles. To demonstrate how to use the Package Admin Service to explicitly resolve a bundle, you’ll create a new resolve command for the shell to instigate bundle resolution, as shown next :
We won’t discuss the details of how you obtain the Package Admin Service until the next chapter; for now, you use the getPackageAdminService() method. If the resolve command is executed with no arguments, you invoke resolveBundles() with null, which causes the framework to attempt to resolve all unresolved bundles. Otherwise, you parse the argument as a list of whitespace-separated bundle identifiers. For each identifier, you get its associated Bundle object and add it to a list. After you’ve retrieved the complete list of bundles, you pass them in as an array to resolveBundles(). The framework attempts to resolve any unresolved bundles of those specified.
Resolving a bundle is a fairly easy process, because the framework does all the hard work for you. You’d think that’d be it. As long as your bundle’s dependencies are resolved, you have nothing to worry about, right? It turns out the dynamic nature of the bundle lifecycle makes this an invalid assumption. Sometimes you need to have the framework recalculate a bundle’s dependencies. We’ll tell you all about it in the next section.
Refreshing bundles :
The lifecycle layer allows you to deploy and manage your application’s bundles. Up until now we’ve focused on installing, resolving, and starting bundles, but there are other interesting bundle lifecycle operations. How about updating or uninstalling a bundle? In and of themselves, these operations are as conceptually simple as the other lifecycle operations. We certainly understand what it means to update or uninstall a bundle. The details are a little more complicated. When you update or uninstall a resolved bundle, you stand a good chance of disrupting your system. This is the place where you can start to see the impact of the framework’s dynamic lifecycle management.
The simple case is updating or uninstalling a self-contained bundle. In this case, the disruption is limited to the specific bundle. Even if the bundle imports packages from other bundles, the disruption is limited to the specific bundle being updated or uninstalled. In either case, the framework stops the bundle if it’s active. In the case of updating, the framework updates the bundle’s content and restarts it if it was previously active. Complications arise if other bundles depend on the bundle being updated or uninstalled. Such dependencies can cause a cascading disruption to your application, if the dependent bundles also have bundles depending on them.
It’s worthwhile to limit the disruptions caused by bundle updates or uninstalls. The framework provides such control by making updating and uninstalling bundles a twostep process. Conceptually, the first step prepares the operation; and the second step, called refreshing, enacts its. Refreshing recalculates the dependencies of the impacted bundles. How does this help? It allows you to control when the changeover to the new bundle version or removal of a bundle occurs for updates and uninstalls, respectively, as shown in figure 3.19 :
We say this is a two-step process, but what happens in the first step? For updates, the new bundle version is put in place, but the old version is still kept around so bundles depending on it can continue loading classes from it. You may be thinking, "Does this mean two versions of the bundle are installed at the same time?" Effectively, the answer is, yes. And each time you perform an update without a refresh, you introduce yet another version. For uninstalls, the bundle is removed from the installed list of bundles, but it isn’t removed from memory. Again, the framework keeps it around so dependent bundles can continue to load classes from it.
For example, imagine you want to update a set of bundles. It would be fairly inconvenient if the framework refreshed all dependent bundles after each individual update. With this two-step approach, you can update all bundles in the set and then trigger one refresh of the framework at the end. You can experience a similar situation if you install a bundle providing a newer version of a package. Existing resolved bundles importing an older version of the package won’t be automatically rewired to the new bundle unless they’re refreshed. Again, it’s nice to be able to control the point in time when this happens. It’s a fairly common scenario when updating your application that some of your bundles are updated, some are uninstalled, and some are installed; so a way to control when these changes are enacted is helpful.
You trigger a refresh by using the Package Admin Service again. To illustrate how to use it, let’s add a refresh command to the shell, as shown next :
The PackageAdmin.refreshPackages() method updates or removes packages exported by the bundles being refreshed. The method returns to the caller immediately and performs the following steps on a separate thread :
As a result of these steps, it’s possible that some of the previously ACTIVE bundles can no longer be resolved; maybe a bundle providing a required package was uninstalled. In such cases, or for any other errors, the framework fires an event of type FrameworkEvent.ERROR.
When updating isn’t updated :
One of the gotchas many people run into when updating a bundle is the fact that it may or may not use its new classes after the update operation. We said previously that updating a bundle is a two-step process, where the first step prepares the operation and the second step enacts it, but this isn’t entirely accurate when you update a bundle. The specification says the framework should enact the update immediately, so after the update the bundle should theoretically be using its new classes; but it doesn’t necessarily start using them immediately. In some situations, after a bundle is updated, new classes are used; in other situations, old classes are used. Sounds confusing, doesn’t it? It is. Why not just wait until a refresh to enact the new revision completely?
The answer, as you might guess, is historical. The original R1 specification defined the update operation to update a bundle. End of story. There was no Package Admin Service. With experience, it became clear that the specified definition of update was insufficient. Too many details were left for framework implementations to decide, such as when to dispose of old classes and start using new classes. Back to the issue of an updated bundle sometimes using old or new classes. There is a way to understand what’s going on. Whether your bundle’s new classes or the old classes are used after an update depends on two factors :
Regarding the first factor :
Supplement :
* [ OSGi In Action ] Introducing OSGi : Learning lifecycle (1)
* [ OSGi In Action ] Introducing OSGi : Learning lifecycle (2)
* [ OSGi In Action ] Introducing OSGi : Learning lifecycle (3)
As you’ll recall from the last chapter, you first converted a nonmodular version of the paint program into a modular one using an interfacebased programming approach for the architecture. This is great because you can reuse the resulting bundles with minimal extra work. The bundles containing the shape implementations don’t need to change, except for some additional metadata in their manifest. You just need to modify the paint program to make it possible for shapes to be added and removed at execution time.
The approach you’ll take is a well-known pattern in the OSGi world, called the extender pattern. The main idea behind the extender pattern is to model dynamic extensibility on the lifecycle events (installing, resolving, starting, stopping, and so on) of other bundles. Typically, some bundle in the application acts as the extender: it listens for bundles being started and/or stopped. When a bundle is started, the extender probes it to see if it’s an extension bundle. The extender looks in the bundle’s manifest (using Bundle.getHeaders()) or the bundle’s content (using Bundle.getEntry()) for specific metadata it recognizes. If the bundle does contain an extension, the extension is described by the metadata. The extender reads the metadata and performs the necessary tasks on behalf of the extension bundle to integrate it into the application. The extender also listens for extension bundles to be stopped, in which case it removes the associated extensions from the application. That’s the general description of the extender pattern, which is shown in figure 3.16. Let’s look at how you’ll use it in the paint program.
You’ll treat the shape implementations as extensions. The extension metadata will be contained in the bundle manifest and will describe which class implements the shape contained in the shape bundle. The extender will use this information to load the shape class from the bundle, instantiate it, and inject it into the application when an extension bundle is activated. If a shape bundle is stopped, the extender will remove it from the application. Figure 3.17 illustrates this usage scenario :
Figure 3.17 Paint program as an implementation of the extender pattern
Let’s dive in and start converting the application. The first thing you need to do is define the extension metadata for shape bundles to describe their shape implementation. In the following snippet, you add a couple of constants to the SimpleShape interface for extension metadata property names; it’s not strictly necessary to add these, but it’s good programming practice to use constants :
- package org.foo.shape;
- import java.awt.Graphics2D;
- import java.awt.Point;
- public interface SimpleShape {
- public static final String NAME_PROPERTY = "Extension-Name";
- public static final String ICON_PROPERTY = "Extension-Icon";
- public static final String CLASS_PROPERTY = "Extension-Class";
- public void draw(Graphics2D g2, Point p);
- }
The name is just a string, and the icon and class refer to a resource file and a class inside the bundle JAR file, respectively. You add similar metadata to the manifests of all shape implementation bundles, which converts them all to extensions. Next, you need to tweak the architecture of the paint program to make it cope with dynamic addition and removal of shapes. Figure 3.18 captures the updated design.
Figure 3.18 Dynamic paint program class relationships
Comparing the new design to the original, you add two new classes: ShapeTracker and DefaultShape. They help you dynamically adapt the paint frame to deal with SimpleShape implementations dynamically appearing and disappearing. In a nutshell, the ShapeTracker is used to track when extension bundles start or stop, in which case it adds or removes DefaultShapes to/from the PaintFrame, respectively.
The concrete implementation of the ShapeTracker is a subclass of another class, called BundleTracker. The latter class is a generic class for tracking when bundles are started or stopped. Because BundleTracker is somewhat long, we’ll divide it across multiple listings; the first part is shown next.
The bundle tracker is constructed with a BundleContext object, which is used to listen for bundle lifecycle events. The tracker uses aSynchronousBundleListener to listen to events because a regular BundleListener doesn’t get notified when a bundle enters the STOPPING state, only STOPPED. You need to react on the STOPPING event instead of the STOPPED event because it’s still possible to use the stopping bundle, which hasn’t been stopped yet; a potential subclass might need to do this if it needed to access the stopping bundle’s BundleContext object. The bundle listener’s single method (1) makes sure the tracker is tracking bundles (2). If so, for started events, it adds the associated bundle to its bundle list (3) and invokes the abstractaddedBundle() method. Likewise, for stopping events, it removes the bundle from its bundle list and invokes the abstract removedBundle() method.
The following listing shows the next portion of the BundleTracker :
To start a BundleTracker instance tracking bundles, you must invoke its open() method. This methods registers a bundle event listener and processes any existing ACTIVE bundles by adding them to its bundle list and invoking the abstract addedBundle() method. The getBundles() method provides access to the current list of active bundles being tracked. Because BundleTracker is abstract, subclasses must provide implementations of addedBundle() andremovedBundle() to perform custom processing of added and removed bundles, respectively.
The last portion of the BundleTracker is as follows :
Calling BundleTracker.close() stops it from tracking bundles. This removes its bundle listener, removes each currently tracked bundle from its bundle list, and invokes the abstract removedBundle() method.
Now that you know how the BundleTracker works, let’s return to its subclass, Shape-Tracker. The heart of this subclass is the processBundle() method shown next, which processes added and removed bundles.
ShapeTracker overrides BundleTracker’s addedBundle() and removedBundle() abstract methods to invoke processBundle() in either case. You determine whether the bundle is an extension by probing its manifest for the Extension-Name property B. Any bundle without this property in its manifest is ignored. If the bundle being added contains a shape, the code grabs the metadata from the bundle’s manifest headers and adds the shape to the paint frame wrapped as a DefaultShape (2). For the icon metadata, you use Bundle.getResource() to load it. If the bundle being removed contains a shape, you remove the shape from the paint frame (3).
DefaultShape, shown in listing 3.18, serves two purposes. It implements the SimpleShape interface and is responsible for lazily creating the shape implementation using the Extension-Class metadata. It also serves as a placeholder for the shape if and when the shape is removed from the application. You didn’t have to deal with this situation in the original paint program, but now shape implementations can appear or disappear at any time when bundles are installed, started, stopped, and uninstalled. In such situations, the DefaultShape draws a placeholder icon on the paint canvas for any departed shape implementations.
In summary, when the paint application is started, its activator creates and opens a ShapeTracker. This tracks STARTED and STOPPED bundle events, interrogating the associated bundle for extension metadata. For every started extension bundle, it adds a new DefaultShape for the bundle to the paint frame, which creates the shape implementation, if needed, using the extension metadata. When the bundle stops, the ShapeTracker removes the shape from the paint frame. When a drawn shape is no longer available, the DefaultShape is used to draw a placeholder shape on the canvas instead. If the departed shape reappears, the placeholder is removed and the real shape is drawn on the canvas again.
Now you have a dynamically extensible paint program, as demonstrated in Part1. Although we didn’t show the activator for the paint program, it’s reasonably simple and only creates the framework and shape tracker on start and disposes of them on stop. Overall, this is a good example of how easy it is to make a modularized application take advantage of the lifecycle layer to make it dynamically extensible. What you’re still missing at this point is a discussion about how the lifecycle and module layers interact with each other, which we’ll get into next.
Lifecycle and modularity :
A two-way relationship exists between OSGi’s lifecycle and module layers. The lifecycle layer manages which bundles are installed into the framework, which obviously impacts how the module layer resolves dependencies among bundles. The module layer uses the metadata in bundles to make sure all their dependencies are satisfied before they can be used. This symbiotic relationship creates a chicken-and-egg situation when you want to use your bundles; to use a bundle you have to install it, but to install a bundle you must have a bundle context, which are only given to bundles. This close relationship is also obvious in how the framework resolves bundle dependencies, especially when bundles are dynamically installed and/or removed. Let’s explore this relationship by first looking into bundle dependency resolution.
Resolving bundles :
The act of resolving a bundle happens at the discretion of the framework, as long as it happens before any classes are loaded from the bundle. Often, when resolving a given bundle, the framework ends up resolving another bundle to satisfy a dependency of the original bundle. This can lead to cascading dependency resolution, because in order for the framework to use a bundle to satisfy the requirements of another bundle, the satisfying bundle too must be resolved, and so on. Because the framework resolves dependencies when needed, it’s possible to mostly ignore transitioning bundles to the RESOLVEDstate; you can start a bundle and know the framework will resolve it before starting it, if possible. This is great compared to the standard Java way, where you can run into missing dependencies at any point during the lifetime of your application.
But what if you want to make sure a given bundle resolves correctly? For example, maybe you want to know in advance whether an installed bundle can be started. In this case, there’s a way to ask the framework to resolve the bundle directly, but it’s not a method on Bundle like most other lifecycle operations. Instead, you use the Package Admin Service. The Package Admin Service is represented as an interface and is shown here :
- public interface PackageAdmin {
- static final int BUNDLE_TYPE_FRAGMENT = 0x00000001;
- Bundle getBundle(Class clazz);
- Bundle[] getBundles(String symbolicName, String versionRange);
- int getBundleType(Bundle bundle);
- ExportedPackage getExportedPackage(String name);
- ExportedPackage[] getExportedPackages(Bundle bundle);
- ExportedPackage[] getExportedPackages(String name);
- Bundle[] getFragments(Bundle bundle);
- RequiredBundle[] getRequiredBundles(String symbolicName);
- Bundle[] getHosts(Bundle bundle);
- void refreshPackages(Bundle[] bundles);
- boolean resolveBundles(Bundle[] bundles);
- }
* Determines which bundle owns a particular class
* Introspects how the framework resolves bundle dependencies
* Refreshes the dependency resolution for bundles
The most important feature of the Package Admin Service isn’t the ability to resolve bundles or introspect dependencies; it’s the ability to refresh bundle dependencies, which is another tool needed for managing bundles. But before we get into the details of refreshing bundles, let’s finish the discussion of explicitly resolving bundles. To demonstrate how to use the Package Admin Service to explicitly resolve a bundle, you’ll create a new resolve command for the shell to instigate bundle resolution, as shown next :
We won’t discuss the details of how you obtain the Package Admin Service until the next chapter; for now, you use the getPackageAdminService() method. If the resolve command is executed with no arguments, you invoke resolveBundles() with null, which causes the framework to attempt to resolve all unresolved bundles. Otherwise, you parse the argument as a list of whitespace-separated bundle identifiers. For each identifier, you get its associated Bundle object and add it to a list. After you’ve retrieved the complete list of bundles, you pass them in as an array to resolveBundles(). The framework attempts to resolve any unresolved bundles of those specified.
Resolving a bundle is a fairly easy process, because the framework does all the hard work for you. You’d think that’d be it. As long as your bundle’s dependencies are resolved, you have nothing to worry about, right? It turns out the dynamic nature of the bundle lifecycle makes this an invalid assumption. Sometimes you need to have the framework recalculate a bundle’s dependencies. We’ll tell you all about it in the next section.
Refreshing bundles :
The lifecycle layer allows you to deploy and manage your application’s bundles. Up until now we’ve focused on installing, resolving, and starting bundles, but there are other interesting bundle lifecycle operations. How about updating or uninstalling a bundle? In and of themselves, these operations are as conceptually simple as the other lifecycle operations. We certainly understand what it means to update or uninstall a bundle. The details are a little more complicated. When you update or uninstall a resolved bundle, you stand a good chance of disrupting your system. This is the place where you can start to see the impact of the framework’s dynamic lifecycle management.
The simple case is updating or uninstalling a self-contained bundle. In this case, the disruption is limited to the specific bundle. Even if the bundle imports packages from other bundles, the disruption is limited to the specific bundle being updated or uninstalled. In either case, the framework stops the bundle if it’s active. In the case of updating, the framework updates the bundle’s content and restarts it if it was previously active. Complications arise if other bundles depend on the bundle being updated or uninstalled. Such dependencies can cause a cascading disruption to your application, if the dependent bundles also have bundles depending on them.
It’s worthwhile to limit the disruptions caused by bundle updates or uninstalls. The framework provides such control by making updating and uninstalling bundles a twostep process. Conceptually, the first step prepares the operation; and the second step, called refreshing, enacts its. Refreshing recalculates the dependencies of the impacted bundles. How does this help? It allows you to control when the changeover to the new bundle version or removal of a bundle occurs for updates and uninstalls, respectively, as shown in figure 3.19 :
We say this is a two-step process, but what happens in the first step? For updates, the new bundle version is put in place, but the old version is still kept around so bundles depending on it can continue loading classes from it. You may be thinking, "Does this mean two versions of the bundle are installed at the same time?" Effectively, the answer is, yes. And each time you perform an update without a refresh, you introduce yet another version. For uninstalls, the bundle is removed from the installed list of bundles, but it isn’t removed from memory. Again, the framework keeps it around so dependent bundles can continue to load classes from it.
For example, imagine you want to update a set of bundles. It would be fairly inconvenient if the framework refreshed all dependent bundles after each individual update. With this two-step approach, you can update all bundles in the set and then trigger one refresh of the framework at the end. You can experience a similar situation if you install a bundle providing a newer version of a package. Existing resolved bundles importing an older version of the package won’t be automatically rewired to the new bundle unless they’re refreshed. Again, it’s nice to be able to control the point in time when this happens. It’s a fairly common scenario when updating your application that some of your bundles are updated, some are uninstalled, and some are installed; so a way to control when these changes are enacted is helpful.
You trigger a refresh by using the Package Admin Service again. To illustrate how to use it, let’s add a refresh command to the shell, as shown next :
The PackageAdmin.refreshPackages() method updates or removes packages exported by the bundles being refreshed. The method returns to the caller immediately and performs the following steps on a separate thread :
As a result of these steps, it’s possible that some of the previously ACTIVE bundles can no longer be resolved; maybe a bundle providing a required package was uninstalled. In such cases, or for any other errors, the framework fires an event of type FrameworkEvent.ERROR.
When updating isn’t updated :
One of the gotchas many people run into when updating a bundle is the fact that it may or may not use its new classes after the update operation. We said previously that updating a bundle is a two-step process, where the first step prepares the operation and the second step enacts it, but this isn’t entirely accurate when you update a bundle. The specification says the framework should enact the update immediately, so after the update the bundle should theoretically be using its new classes; but it doesn’t necessarily start using them immediately. In some situations, after a bundle is updated, new classes are used; in other situations, old classes are used. Sounds confusing, doesn’t it? It is. Why not just wait until a refresh to enact the new revision completely?
The answer, as you might guess, is historical. The original R1 specification defined the update operation to update a bundle. End of story. There was no Package Admin Service. With experience, it became clear that the specified definition of update was insufficient. Too many details were left for framework implementations to decide, such as when to dispose of old classes and start using new classes. Back to the issue of an updated bundle sometimes using old or new classes. There is a way to understand what’s going on. Whether your bundle’s new classes or the old classes are used after an update depends on two factors :
Regarding the first factor :
Supplement :
* [ OSGi In Action ] Introducing OSGi : Learning lifecycle (1)
* [ OSGi In Action ] Introducing OSGi : Learning lifecycle (2)
* [ OSGi In Action ] Introducing OSGi : Learning lifecycle (3)
This message was edited 29 times. Last update was at 15/07/2011 17:50:37
沒有留言:
張貼留言