程式扎記: [ OSGi In Action ] Introducing OSGi : Learning lifecycle (1)

標籤

2011年7月16日 星期六

[ OSGi In Action ] Introducing OSGi : Learning lifecycle (1)

Preface : 
This chapter covers 

■ Understanding software lifecycle management
■ Introducing the lifecycle of a bundle
■ Exploring the lifecycle layer API
■ Extending an application to make it lifecycle aware
■ Explaining the relationship between the module and lifecycle layers

The OSGi module layer goes to great lengths to ensure that class loading happens in a consistent and predictable way. But to avoid putting the cart before the horse, in chapter 2 we glossed over the details of how you install bundles into an OSGi framework. No longer: in this chapter, we’ll look at the next layer of the OSGi stack—the lifecycle layer. 

As we mentioned before, to use a bundle you install it into a running instance of the OSGi framework. So creating a bundle is the first half of leveraging OSGi’s modularity features; the second half is using the OSGi framework as a runtime to manage and execute bundles. The lifecycle layer is unique in allowing you to create externally (and remotely) managed applications or completely self-managed applications (or any combination of the two). It also introduces dynamism that isn’t normally part of an application. 

This chapter will familiarize you with the features of the lifecycle layer and explain how to effectively use them. In the next section, we’ll take a closer look at what lifecycle management is and why you should care about it, followed by the definition of the OSGi bundle lifecycle. In subsequent sections, you’ll learn about the API for managing the lifecycle of bundles. Throughout this chapter, we’ll bring all the points home via examples of a simple OSGi shell and a lifecycle-aware version of the paint program. 

Introducing lifecycle management : 
The OSGi lifecycle layer provides a management API and a well-defined lifecycle for bundles at execution time in the OSGi framework. The lifecycle layer serves two different purposes : 

* External to your application, the lifecycle layer precisely defines the bundle lifecycle operations. These lifecycle operations allow you to manage and evolve your application by dynamically changing the composition of bundles inside a running framework.

* Internal to your application, the lifecycle layer defines how your bundles gain access to their execution context, which provides them with a way to interact with the OSGi framework and the facilities it provides at execution time.

But let’s take a step back. It’s fine to state what the OSGi lifecycle layer does, but this won’t necessarily convince you of its worth. Instead, let’s look at a quick example of how it can improve your applications with a real-world scenario. 

What is lifecycle management? 
Imagine you have a business application that can report management events via JMX. Do you always want to enable or even install the JMX layer? Imagine running in a lightweight configuration and only enabling the JMX notifications on demand. The lifecycle layer allows you to install, start, update, stop, and uninstall different bundles externally, to customize your application’s configuration at execution time. 

Further, imagine that a critical failure event in your application must trigger the JMX layer to send out a notification regardless of whether the administrator previously enabled or installed the layer. The lifecycle layer also provides programmatic access to bundles so they can internally modify their application’s configuration at execution time. 

Generally speaking, programs (or parts of a program) are subject to some sort of lifecycle, which may or may not be explicit. The lifecycle of software typically has four distinct phases, as shown in figure 3.1. 
 
Figure 3.1 The four phases of the software lifecycle. An application is installed so you can execute it. Later, you can update it to a newer version or, ultimately, remove it if you no longer need it. 

If you’re creating an application, think about the typical lifecycle of the application as a whole. First you need to install it. Assuming all its dependencies are satisfied, you can execute it, which allows it to begin acquiring resources. When the application is no longer needed, you stop it, which allows it to release any resources and perhaps persist any important state. Over time, you may want to update the application to a newer version. Ultimately, you may remove the application because you no longer need it. For nonmodular applications, the lifecycle operates on the application as a whole; but as you’ll see, for modular applications, fine-grained lifecycle management is possible for individual pieces of the application. 

The following are two of the more popular models for creating applications in Java and how they manage software lifecycle : 
- Standard Java 

For the purposes of this discussion, we’ll equate an application in standard Java to a JAR file containing the Main-Class header in its manifest, which allows it to be easily executed. In standard Java development, the lifecycle of an application is simple. Such a JAR-based Java application is installedwhen downloaded. It’s executed when the user launches a JVM process, typically by double-clicking it. The application is stopped when the program terminates. Updating is usually done by replacing the JAR with a newer version. Removal is achieved by deleting the JAR from the file system.

- Servlet 

In servlet development, the lifecycle of the web application is managed by the servlet container. The application is installed via a container-specific process; sometimes this involves dropping a WAR file containing the application in a certain directory in the file system or uploading a WAR file via a web-management interface. The servlet container calls various lifecycle API methods such as Servlet.init() and Servlet.destroy() on the WAR file’s subcomponents during the execution phase of the application’s lifecycle. To update the application, a completely new WAR file is generated. The existing WAR must be stopped and the new WAR file started in its place. The application is removed by a container-specific process, again sometimes removing the WAR from the file system or interacting with a management interface.

As you know, many different lifecycle-management approaches are used in Java today. In traditional Java applications, the lifecycle is largely managed by the platform-specific mechanism of the underlying operating system via installers and double-clicking desktop icons. For modular development approaches, such as servlets, Java EE, and Net-Beans, each has its own specific mechanism of handling the lifecycle of its components. This leads us to the question of why you need lifecycle management at all. 

Why lifecycle management? 
We talked about the benefits of separating different concerns into bundles and avoiding tight coupling among them. An explicit lifecycle API lets the providing application take care of how to configure, initialize, and maintain a piece of code that’s installed so it can decide how it should operate at execution time. Because the OSGi specification provides an explicit lifecycle API, you can take any bundle providing the functionality you need and let it worry about how to manage its internal functions. In essence, it’s a matter of compose versus control. 

Because you can architect your application such that parts of it may come and go at any point in time, the application’s flexibility is greatly increased. You can easily manage installation, update, and removal of an application and its required modules. You can configure or tailor applications to suit specific needs, breaking the monolithic approach of standard development approaches. Instead of “you get what you get,” wouldn’t it be great if you could offer “you get what you need”? 

Another great benefit of the standard lifecycle API is that it allows for a diverse set of management applications that can manage your application. There’s no magic going on; lifecycle management can be done completely using the provided API. Now, let’s focus specifically on defining the OSGi bundle lifecycle and the management API associated with it. 

OSGi bundle lifecycle : 
The module metadata from chapter 2 is all well and good, but creating bundles in and of itself is useful only if you use them. You need to interact with the OSGi lifecycle layer in order to use the bundles. Unlike the module layer, which relies on metadata, the lifecycle layer relies on APIs. We’ll move in a top-down fashion and use an example to show what the lifecycle layer API allows you to do. 

It’s important to note that the OSGi core framework doesn’t mandate any particular mechanism of interacting with the lifecycle API (such as the command line, a GUI, or an XML configuration file); the core is purely a Java API. This turns out to be extremely powerful, because it makes it possible to design as many different ways of managing the OSGi framework as you can think of. We’ll lead you through some basic steps for developing your own command line interface for interacting with the OSGi framework. This gives you the perfect tool, alongside the paint program, to explore the rich capabilities provided by the OSGi lifecycle API. 
 

Introducing lifecycle to the paint program : 
let’s see the lifecycle API in action by kicking off the shell application and using it to install the paint program. Here I use the Apache karaf as OSGi framework to demo here. Firstly, follow below steps for demo (Windows users, substitute \ for /) : 
Step1 : Firstly, install paint program into OSGi framework and stop them. Type "osgi:list" or "list" to see existent bundles : 
 

Step2 : Using command "osgi:start" or "start" to start bundle paint(3.0) : 
 

Step3 : Start bundles "shape" and "circle" 
 

Step4 : Start bundles "triangle" and "square" 
 

Step5 : Stop bundle "triangle" 
 

This shows you in practice that you can use the lifecycle API to build a highly dynamic application, but what’s going on in this example? To understand, we’ll take a topdown approach, using the shell and paint example for context. 

The OSGi framework’s role in the lifecycle : 
In standard Java programming, you use JAR files by placing them on the class path. This isn’t the approach for using bundles. A bundle can only be used when it’s installed into a running instance of the OSGi framework. Conceptually, you can think of installing a bundle into the framework as being similar to putting a JAR file on the class path in standard Java programming. 

This simplified view hides some important differences from the standard class path, as you can see in figure 3.3. One big difference is the fact that the OSGi framework supports full lifecycle management of bundle JAR files, including install, resolve, start, stop, update, and uninstall. At this point, we’ve only touched on installing bundles and resolving their dependencies. The remainder of this chapter will fully explain the lifecycle activities and how they’re related to each other. For example, we’ve already mentioned that the framework doesn’t allow an installed bundle to be used until its dependencies (Import-Package declarations) are satisfied. 
 

Another huge difference from the standard class path is inherent dynamism. The OSGi framework supports the full bundle lifecycle at execution time. This is similar to modifying what’s on the class path dynamically. As part of lifecycle management, the framework maintains a persistent cache of installed bundles. This means the next time you start the framework, any previously installed bundles are automatically reloaded from the bundle cache, and the original JAR files are no longer necessary. Perhaps we can characterize the framework as a fully manageable, dynamic, and persistent class path. Sounds cool, huh? Let’s move on to how you have to modify the metadata to allow bundles to hook into the lifecycle layer API. 

The bundle activator manifest entry : 
How do you tell the framework to kick-start the bundles at execution time? The answer, as with the rest of the modularity information, is via the bundle metadata. Here’s the JAR file manifest describing the shell bundle you’ll create : 

Manifest-Version: 1.0
Bundle-ManifestVersion: 2
Bundle-SymbolicName: org.foo.shell
Bundle-Version: 1.0

Bundle-Activator: org.foo.shell.Activator
Import-Package: org.osgi.framework;version="[1.3,2.0)",
org.osgi.service.packageadmin;version="[1.2,2.0)",
org.osgi.service.startlevel;version="[1.1,2.0)"
Bundle-Name: remote_shell
Bundle-DocURL: http://code.google.com/p/osgi-in-action/

You should already be familiar with most of these headers from chapter 2. But to recap, most of the entries are related to the class-level modularity of the bundle. This metadata does the following : 

■ Defines the bundle’s identity
■ Specifies the packages on which this bundle depends
■ Declares additional human-readable information

The only new header is Bundle-Activator. This is the first sighting of the OSGi lifecycle API in action! The Bundle-Activator header specifies the name of a reachable class (that is, either imported or on the bundle class path) implementing the org.osgi.framework.BundleActivator interface. This interface provides the bundle with a hook into the lifecycle layer and the ability to customize what happens when it’s started or stopped. 

Is an activator necessary?
Keep in mind that not all bundles need an activator. An activator is necessary only if you’re creating a bundle and wish to specifically interact with OSGi API or need to perform custom initialization/de-initialization actions. If you’re creating a simple library bundle, it isn’t necessary to give it an activator because it’s possible to share classes without one.
This doesn’t mean your bundles won’t be able to do anything. Bundles don’t necessarily need to be started in order to do useful things. Remember the paint program you created in chapter 2: none of the bundles had activators, nor did any of them need to be started, but you still created a fully functioning application.

To understand what’s going on in the shell example, we’ll now introduce you to three interfaces (BundleActivatorBundleContext, and Bundle) that are the heart and soul of the lifecycle layer API. 

Introducing the lifecycle API : 
The last section described how the shell bundle declares a BundleActivator to hook into the framework at execution time. We can now look into the details of this interface and the other lifecycle APIs made available from it to the bundle. This is the bundle’s hook into the world of OSGi. 

- BUNDLE ACTIVATOR 
As you’ve seen, adding an activator to the bundle is straightforward, because you only need to create a class implementing the BundleActivator interface, which looks like this : 

  1. public interface BundleActivator {  
  2.     public void start(BundleContext context) throws Exception;  
  3.     public void stop(BundleContext context) throws Exception;  
  4. }  
For the shell example, the activator allows it to become lifecycle aware and gain access to framework facilities. The following listing shows the activator for the shell bundle. 

- Listing 3.1 Simple shell bundle activator :
  1. package org.foo.shell;  
  2.   
  3. import org.osgi.framework.BundleActivator;  
  4. import org.osgi.framework.BundleContext;  
  5.   
  6. public class Activator implements BundleActivator {  
  7.     private volatile Binding m_binding; // 1. Declares volatile member field  
  8.   
  9.     public void start(BundleContext context) {  
  10.         int port = getPort(context);  // 2. Use context to get configuration  
  11.         int max = getMaxConnections(context);  
  12.         m_binding = getTelnetBinding(context, port, max); // 3. Passes context into telnet binding  
  13.         m_binding.start();  // 4. Starts binding  
  14.         System.out.println("Bundle " + context.getBundle().getSymbolicName()  
  15.                 + " started with bundle id" + context.getBundle().getBundleId()  
  16.                 + " listening on port " + port);  
  17.     }  
  18.   
  19.     public void stop(BundleContext context) {  
  20.         m_binding.stop();  
  21.     }  
  22.     // ...  
  23. }  
  24. public interface Binding {  
  25.     public void start();  
  26.     public void stop() throws InterruptedException;  
  27. }  

This class implements the OSGi BundleActivator interface. When the bundle is installed and started, the framework constructs an instance of this activator class and invokes the start() method. When the bundle is stopped, the framework invokes the stop() method. The start() method is the starting point for your bundle, sort of like the static main() method in standard Java. After it returns, your bundle is expected to function until the stop() method is invoked at some later point. The stop() method should undo anything done by the start() method. 

We need to mention a few technical but potentially important details about the handling of the BundleActivator instance : 

* The activator instance on which start() is called is the same instance on which stop() is called.
* After stop() is called, the activator instance is discarded and isn’t used again.
* If the bundle is subsequently restarted after being stopped, a new activator instance is created, and the start() and stop() methods are invoked on it as appropriate.

As you can see, the rest of the activator isn’t complicated. In the start() method, you get the port on which the bundle listens for connection requests and the number of allowed concurrent connections. You also create a TelnetBinding, which does the work of listening on a socket for user input and processes it; the details of creating the telnet binding are omitted here for reasons of simplicity and brevity. The next step is to start the binding, which creates a new Thread to run the shell. 

The point about the binding starting its own thread is important because the activator methods shouldn’t do much work. This is best practice as with most callback patterns, which are supposed to return quickly, allowing the framework to carry on managing other bundles. But it’s also important to point out that the OSGi specification doesn’t mandate you start a new thread if your application’s startup doesn’t warrant it—the ball is in your court. 

For the activator stop() method, all you do is tell the binding to stop listening to user input and cease to execute. You should make sure it does stop by waiting until its thread is finished; the binding method waits for its thread to stop. Sometimes, you may have special cases for certain situations because, as you’ll see later, the shell thread itself may call the stop() method, which will cause the bundle to freeze. We’ll cover these and other advanced use cases later. In general, if you use threads in your bundles, do so in such a way that all threads are stopped when the stop() method returns. 
 

- BUNDLE CONTEXT 
As you learned in the previous section, the framework calls the start() method of a bundle’s activator when it’s started and the stop() method when it’s stopped. Both methods receive an instance of the BundleContext interface. The methods of the BundleContext interface can be roughly divided into two categories : 

■ The first category is related to deployment and lifecycle management.
■ The second category is related to bundle interaction via services.

We’re interested in the first category of methods, because they give you the ability to install and manage the lifecycle of other bundles, access information about the framework, and retrieve basic configuration properties. This listing captures these methods from BundleContext : 

- Listing 3.2 BundleContext methods related to lifecycle management :
  1. public interface BundleContext {  
  2.     ...  
  3.     String getProperty(String key);  
  4.     Bundle getBundle();  
  5.     Bundle installBundle(String location, InputStream input)  
  6.     throws BundleException;  
  7.     Bundle installBundle(String location) throws BundleException;  
  8.     Bundle getBundle(long id);  
  9.     Bundle[] getBundles();  
  10.     void addBundleListener(BundleListener listener);  
  11.     void removeBundleListener(BundleListener listener);  
  12.     void addFrameworkListener(FrameworkListener listener);  
  13.     void removeFrameworkListener(FrameworkListener listener);  
  14.     ...  
  15. }  

We’ll cover most of these methods in this chapter. The second category of Bundle-Context methods related to services will be covered in the next chapter. 
 

The shell activator in listing 3.1 uses the bundle context to get its configuration property values (2). It also passes the context into the telnet binding (3), which client connections will use to interact with the running framework. Finally, it uses the context to obtain the bundle’s Bundle object to access the identification information. We’ll look at these details shortly, but for now we’ll continue the top-down description by looking at the final lifecycle layer interface : org.osgi.framework.Bundle. 

- BUNDLE 
For each installed bundle, the framework creates a Bundle object to logically represent it. The Bundle interface defines the API to manage an installed bundle’s lifecycle; a portion of the interface is presented in the following listing. As we discuss the Bundle interface, you’ll see that most lifecycle operations have a corresponding method in it. 

- Listing 3.3 Bundle interface methods related to lifecycle management
  1. public interface Bundle {  
  2.     ...  
  3.     BundleContext getBundleContext();  
  4.     long getBundleId();  
  5.     Dictionary getHeaders();  
  6.     Dictionary getHeaders(String locale);  
  7.     String getLocation();  
  8.     int getState();  
  9.     String getSymbolicName();  
  10.     Version getVersion();  
  11.     void start(int options) throws BundleException;  
  12.     void start() throws BundleException;  
  13.     void stop(int options) throws BundleException;  
  14.     void stop() throws BundleException;  
  15.     void update(InputStream input) throws BundleException;  
  16.     void update() throws BundleException;  
  17.     void uninstall() throws BundleException;  
  18.     ...  
  19. }  

Each installed bundle is uniquely identified in the framework by its Bundle object. From the Bundle object, you can also access two additional forms of bundle identification: the bundle identifier and the bundle location. The identification metadata in chapter 2 was for static identification of the bundle JAR file. The bundle identifier and bundle location are for execution-time identification, meaning they’re associated with the Bundle object. The main difference between the two is who defines the identifier; see figure 3.4. The bundle identifier is a Java language long value assigned by the framework in ascending order as bundles are installed. The bundle location is a String value assigned by the installer of the bundle. 
 
 

Although one instance of Bundle exists for each bundle installed into the framework, at execution time there’s also a special instance of Bundle to represent the framework itself. This special bundle is called the system bundle; and although the API is the same, it merits its own discussion. 

- THE SYSTEM BUNDLE 
At execution time, the framework is represented as a bundle with an identifier of 0, called the system bundle. You don’t install the system bundle—it always exists while the framework is running. The system bundle follows the same lifecycle as normal bundles, so you can manipulate it with the same operations as normal bundles. But lifecycle operations performed on the system bundle have special meanings when compared to normal bundles. One example of the special meaning is evident when you stop the system bundle. Intuitively, stopping the system bundle shuts down the framework in a wellbehaved manner. It stops all other bundles first and then shuts itself down completely. 

We conclude our high-level look at the major API players in the lifecycle layer (BundleActivatorBundleContext, and Bundle). You now know the following : 

■ BundleActivator is the entry point for the bundles, much like static main() in a standard Java application.
■ BundleContext provides applications with the methods to manipulate the OSGi framework at execution time.
■ Bundle represents an installed bundle in the framework, allowing state manipulations to be performed on it.

We’ll complete the top-down approach by defining the overall bundle lifecycle state diagram and see how these interfaces relate to it. 

Lifecycle state diagram : 
Until now, we’ve been holding off on explicitly describing the complete bundle lifecycle in favor of getting a high-level view of the API forming the lifecycle layer. Now we can better understand how these APIs relate to the complete bundle lifecycle state diagram, shown in figure 3.5 : 
 

The entry point of the bundle lifecycle is the BundleContext.installBundle() operation, which creates a bundle in the INSTALLED state. From figure 3.5, you can see that there’s no direct path from INSTALLED to STARTING. This is because the framework ensures all dependencies of a bundle are satisfied before it can be used. The transition from the INSTALLED to the RESOLVED state represents this guarantee. The framework won’t allow a bundle to transition toRESOLVED unless all its dependencies are satisfied. If it can’t transition to RESOLVED, by definition it can’t transition to STARTING. Often, the transition toRESOLVED happens implicitly when the bundle is started or another bundle tries to load a class from it, but you’ll see later in this chapter that it’s also possible to explicitly resolve a bundle. 

The transition from the STARTING to the ACTIVE state is always implicit. A bundle is in the STARTING state while its activator’s start() method executes. Upon successful completion of the start() method, the bundle’s state transitions to ACTIVE; but if the activator throws an exception, it transitions back toRESOLVED. 

An ACTIVE bundle can be stopped, which also results in a transition back to the RESOLVED state via the STOPPING state. The STOPPING state is an implicit state like STARTING, and the bundle is in this state while its activator’s stop() method executes. A stopped bundle transitions back to RESOLVED instead ofINSTALLED because its dependencies are still satisfied and don’t need to be resolved again. It’s possible to force the framework to resolve a bundle again by refreshing it or updating it, which we’ll discuss later. Refreshing or updating a bundle transitions it back to the INSTALLED state. 

A bundle in the INSTALLED state can be uninstalled, which transitions it to the UNINSTALLED state. If you uninstall an active bundle, the framework automatically stops the bundle first, which results in the appropriate state transitions to the RESOLVED state and then transitions it to the INSTALLED state before uninstalling it.1 A bundle in the UNINSTALLED state remains there as long as it’s still needed (we’ll explain later what this means), but it can no longer transition to another state. Now that you understand the complete bundle lifecycle, let’s discuss how these operations impact the framework’s bundle cache and subsequent restarts of the framework. 

Bundle cache and framework restarts : 
To use bundles, you have to install them into the OSGi framework. But what does this mean? Technically, you know you must invokeBundleContext.installBundle() to install a bundle. In doing so, you must specify a location typically interpreted as a URL to the bundle JAR file or an input stream from which the bundle JAR file is read. In either case, the framework reads the bundle JAR file and saves a copy in a private area known as the bundle cache. This means two things : 

■ Installing a bundle into the framework is a persistent operation.
■ After the bundle is installed, the framework no longer needs the original copy of the bundle JAR file.

The exact details of the bundle cache are dependent on the framework implementation; the specification doesn’t dictate the format nor structure other than that it must be persistent across framework executions. If you start an OSGi framework, install a bundle, shut down the framework, and then restart it, the bundle you installed will still be there, as shown in figure 3.6. If you compare this approach to using the class path, where you manually manage everything, having the framework cache and manage the artifacts relieves you of a lot of effort. 
 

Bundle.update() and Bundle.uninstall() impact the bundle cache by saving a new bundle JAR file or removing an existing bundle JAR file, respectively. But these operations may not affect the cache immediately. We’ll explain these oddities when we discuss the relationship between the modularity and lifecycle layers in section 3.5. Next, we’ll delve into the details of the shell bundle as we more fully explore how to use the lifecycle layer API. 



Supplement :
- 3.1 Introducing lifecycle management
- 3.2 OSGi bundle lifecycle

- 3.3 Using the lifecycle API in your bundles
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...

- 3.4 Dynamically extending the paint program
- 3.5 Lifecycle and modularity
This message was edited 29 times. Last update was at 15/07/2011 17:50:37

沒有留言:

張貼留言

網誌存檔

關於我自己

我的相片
Where there is a will, there is a way!