4.8. Advanced Examples

We now present more elaborate examples of how to develop bundles. We also want to point out the idea that this new model does not limit you from leveraging the full power of the Java platform: You can use extensions such as JavaMail™ and Java servlets. You can apply object-oriented abstraction of any complexity in your service implementation.

4.8.1. A Mailer

This bundle mainly demonstrates how to deal with nested library JAR files. It uses the JavaMail API[3] to send out an e-mail message when it is started or stopped.

[3] You can download the JavaMail class library (packaged in mail.jar) from http://ja_va.sun.com/products/javamail/index.html. Note that JavaMail requires the JavaBeans™ Activation Framework (packaged in activation.jar).

Although e-mail delivery appears to be an activity performed by humans at desktop computers, it can lend itself to interesting applications on a residential gateway. For example, a home security bundle can notify you if it detects an intruder on the premises by sending an e-mail message to your pager.

To use the JavaMail API in your code, you first obtain the javax.mail.Session object, which encapsulates various environment properties for the e-mail application, such as the name of the mail server. With the Session object, you can create javax.mail.internet.MimeMessage, in which you specify the sender, recipient, subject, and body of the e-mail message. Lastly, use javax.mail.Transport's send method to deliver the e-mail message.

The JavaMail API makes use of the JavaBeans™ Activation Framework (JAF), which is concerned with assigning data content handlers to particular data types. The familiar “mailcap” specification (RFC 1524 [13]) is one concrete example of the JAF. For more details on JavaMail and JAF, please consult related white papers and Java documentation [14].

We develop the mail sender bundle first. The code that implements its activator is as follows:

package com.acme.mail;
import java.io.*;
import java.net.InetAddress;
import java.util.Properties;
import java.util.Date;
import javax.mail.*;
import javax.mail.internet.*;
import org.osgi.framework.*;
/**
 * This class delivers an email notification
 * when the bundle is started or stopped.
 */
public class Notification implements BundleActivator {
   public void start(BundleContext ctxt) throws Exception {
      send("Home, sweet home!");
   }

   public void stop(BundleContext ctxt) throws Exception {
      send("I'll be back!");
   }

   private void send(String msgBody) throws Exception {
      String to = System.getProperty("com.acme.mail.recipient");
      String subject = "JavaMail library bundle example";
      String from = System.getProperty("com.acme.mail.sender");
      String mailhost =
         System.getProperty("com.acme.mail.server");
      String mailer = "JES Mailer Bundle";
      Properties props = System.getProperties();
      props.put("mail.smtp.host", mailhost);

      // Get a Session object
      Session session = Session.getDefaultInstance(props, null);

      // construct the message
      Message msg = new MimeMessage(session);
      msg.setFrom(new InternetAddress(from));
      msg.setRecipients(Message.RecipientType.TO,
              InternetAddress.parse(to, false));
      msg.setSubject(subject);
      msg.setText(msgBody);
      msg.setHeader("X-Mailer", mailer);
      msg.setSentDate(new Date());

      // send the thing off
      Transport.send(msg);
    }
}

This mail sender bundle has the following manifest:

Import-Package: javax.mail, javax.mail.internet, javax.activation
Bundle-Activator: com.acme.mail.Notification

We do not specify package version numbers in the Import-Package header, effectively importing any versions of the packages that have been exported. The bundle contains

META-INF/MANIFEST.MF
com/acme/mail/Notification.class

We then create the mail library bundle. The JavaMail API and the JAF come in two JAR files: mail.jar and activation.jar. Conventionally these JAR files are expected to be included in your CLASSPATH, but here we nest them inside a separate mail class library bundle. We could package the library and the code that uses it into a single bundle, but keeping the former self-contained in its own bundle allows it to be shared by other bundles without much overhead. We then define the following manifest file to export the packages:

Export-Package: javax.mail; specification-verion=1.1.3,
 javax.mail.internet; specification-version=1.1.3,
 javax.activation; specification-version=1.0.1
Bundle-ClassPath: mail.jar, activation.jar

We declare that the library bundle exports three packages from the JavaMail API and the JAF. Furthermore, we also specify the version numbers for these packages so that the framework can pick the appropriate exporters. As a rule, it is recommended that exporters always specify versions of packages to be exported. Importers, on the other hand, don't have to, unless they must import one particular version. We do not include the dot (.) in Bundle-ClassPath because there is no other class present in the mail library bundle. For the same reason, we have no Bundle-Activator manifest header either.

The contents of the newly created library bundle are simply

META-INF/MANIFEST.MF
mail.jar
activation.jar

Install and activate the mail library bundle first, then install the mail sender bundle. Set the mailer's parameters using the set command from the Java Embedded Server console like this:

> set [email protected]
> set [email protected]
> set com.acme.mail.server=mail.acme.com

Then you can activate and deactivate the bundle to have the e-mail messages sent.

4.8.2. A Line Printer Daemon Print Service

In this example we develop a print service. The complete source code is located in Appendix A.1.1. Here, we show the part of the code that is instructive to our discussion.

First and foremost, we present the PrintService interface:

package com.acme.service.print;
import java.io.IOException;
import java.net.URL;
/**

 * PrintService prints contents from a URL or
 * gets status of a print queue.
 */
public interface PrintService {
   /**
    * Prints the contents from the source to the specified printer.
    * @param printer the name of the destination printer
    * @param source the source of the contents to be printed
    * @exception IOException if printing fails
    */

   public void print(String printer, URL source) throws IOException;

   /**
    * Gets the status of the specified printer.
    * @param printer the name of the printer
    * @return the status of the print queue as an array,
    * one element per print job;
    * null if no entries are present in the queue
    */
   public String[] getStatus(String printer) throws IOException;
}

This is a generic interface that specifies two major features pertinent to a printer: printing some contents from a source URL and obtaining the status of the print queue. It is not difficult to propose a multitude of possible implementation scenarios. For example, one implementation may use a proprietary protocol to access a local printer through a printer driver installed in the operating system; another may use a standard network protocol to communicate with a printer on the network.

In this case, let's implement the service interface with the LPD protocol, which interacts with the printer over the TCP/IP network. This protocol has been widely used in many UNIX environments and can be installed and configured on Windows NT platforms as well. The protocol is specified in RFC 1179 [15].

We implement the service as an LPD client that connects to a remote print server on the well-known port of 515. The setup is illustrated in Figure 4.3.

Figure 4.3. The LPD printer


Based on the request/response nature of the protocol, we provide further abstraction in our service implementation. We first initialize a Printer object with the name of the printer server. Whenever we want to request a function (such as getting status or printing), we call the request method on Printer:

package com.acme.impl.print;
import java.io.*;
import java.net.*;

class Printer {
   private String printServer;
   Printer(String ps) {
      this.printServer = ps;
   }

   /**
    * Send a request to the printer daemon.
    * @param command a number representing a command
    * @param queue the name of the print queue
    * @param operand the operand needed by the command
    * @return a connection to the printer daemon.
    * @exception java.io.IOException if communication to the daemon
    * fails.
    */
   PrintConnection request(int command, String queue,
      String operand) throws IOException
   {
      // Open a socket on the print server at port 515;
      // send the request in the format defined by the RFC;
      // return a PrintConnection object that encapsulates the
      // connection to the print server, so that the required
      // information can be exchanged to complete the request.
   }
}

According to the RFC, to get status, send the sequence of protocol elements depicted in Figure 4.4 to the daemon.

Figure 4.4. Protocol operation for getting print queue status. In the request sequence, the constant symbol SEND_QUEUE_STATUS is 4 in the program, SP is a white space (ASCII 32), queue is the print queue name, and LF is the line feed character (ASCII 10). The status is returned by the daemon as strings.


A single round-trip is sufficient to retrieve the print queue status. However, a few exchanges are needed to deliver a print job, as shown in Figure 4.5

Figure 4.5. Protocol operation for sending a print job to the daemon. The request has a similar format. The constant symbol RECEIVE_PRINT_JOB has a value of 2 in the program. This request is followed by a negotiation consisting of subcommands RECEIVE_CONTROL and RECEIVE_DATA.


PrintConnection encapsulates a session between the client and the server during the process of fulfilling a request:

package com.acme.impl.print;
import java.net.*;
import java.io.*;

class PrintConnection {
   private static int RECEIVE_CONTROL = 2;
   private static int RECEIVE_DATA = 3;
   private Socket sock;
   private String host;
   private String printQueue;

   PrintConnection(String q, Socket s) throws IOException {
      this.sock = s;
      this.host = InetAddress.getLocalHost().getHostName();
      this.printQueue = q;
   }

   // Get a character stream from the printer daemon.
   Reader getReader() throws IOException {
      InputStream in = sock.getInputStream();
      return new BufferedReader(new InputStreamReader(in));
   }
   // Get a byte stream from the printer daemon.
   InputStream getInputStream() throws IOException {
      return sock.getInputStream();
   }

   // Get an acknowledge byte from the daemon. 0 means success.
   int getAck() throws IOException {
      return sock.getInputStream().read();
   }

   // Send control sequences to the daemon. This identifies the data
   // that follow and their size.
   void sendControl(int jobNumber, URL src) throws IOException
   {
      // Construct the control sequence as defined by the RFC.
      // Request to send the control sequence to the printer.
      // Send the contents of the control sequence to the printer.
      }

   // Send data to the daemon. The data source can be a local file
   // or from a URL pointing to a remote location.
   void sendData(int jobNumber, URL src) throws IOException {
      // Read from the specified data source.
      // Request to send data to the printer.
      // Send the data to the printer.
   }

   // Close connection to the daemon.
   void close() throws IOException {
      sock.close();
   }
}

With the Printer and PrintConnection classes defined, we are ready to implement the PrintService interface with the PrintServiceImpl class. The getStatus method implements the protocol operation of getting the print queue status; the print method implements the protocol operation of delivering a print job to the printer daemon:

package com.acme.impl.print;
import java.net.*;
import java.io.*;
import java.util.*;
import com.acme.service.print.PrintService;

class PrintServiceImpl implements PrintService {
   private final static int SEND_QUEUE_STATE = 4;
   private final static int RECEIVE_PRINT_JOB = 2;
   private Printer printer;
   private static int jobNumber = 100;

   PrintServiceImpl() {
      String lpdServer =
         System.getProperty("com.acme.service.print.lpdserver");
      printer = new Printer(lpdServer);
   }

   public String[] getStatus(String printQueue) throws IOException {
      // Request print job status from the printer
      // Process the report from the printer and return the result
   }

   public void print(String printQueue, URL src)
      throws IOException
   {
      // Request to send print job to the printer
      // Send control sequence describing the print job
      // Send the data to be printed.
   }
}

At this point we have completed our LPD PrintService implementation. The next order of business is to write the bundle activator:

package com.acme.impl.print;
import java.util.Properties;
import org.osgi.framework.*;
import com.acme.service.print.PrintService;

public class Activator implements BundleActivator {
   private ServiceRegistration reg;
   public void start(BundleContext ctxt) {
      PrintService svc = new PrintServiceImpl();
      Properties props = new Properties();
      props.put("description", "A sample print service");
      reg = ctxt.registerService(
         "com.acme.service.print.PrintService", svc, props);
   }

   public void stop(BundleContext ctxt) {
      if (reg != null)
         reg.unregister();
   }
}

We then define the manifest as follows:

Bundle-Activator: com.acme.impl.print.Activator
Export-Package: com.acme.service.print

We now use the jar command to zip the classes into a bundle. The contents of this bundle will look like

META-INF/MANIFEST.MF
com/acme/service/print/PrintService.class
com/acme/impl/print/Activator.class
com/acme/impl/print/PrintServiceImpl.class
com/acme/impl/print/Printer.class
com/acme/impl/print/PrintConnection.class

The process of implementing the print service is not unlike conventional programming using the Java programming language. It is up to you to decide what types of class hierarchy should be constructed, what kind of data structures must be written, and so on.

Moreover, development of the service implementation is mostly confined in a particular application domain. In other words, you are free to program whatever is necessary to bring about the desired functionality. The OSGi framework itself and its programming paradigm do not restrict what can be done in one particular application domain. You only need to follow some simple rules to make your code an accepted “citizen” in the OSGi world.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.224.51.145