Return-Path: Received: from pacific-carrier-annex.mit.edu by po10.mit.edu (8.9.2/4.7) id UAA03420; Wed, 22 Jan 2003 20:39:26 -0500 (EST) Received: from hermes.sun.com (hermes.sun.com [64.124.140.169]) by pacific-carrier-annex.mit.edu (8.9.2/8.9.2) with SMTP id UAA22411 for ; Wed, 22 Jan 2003 20:39:25 -0500 (EST) Date: 22 Jan 2003 15:47:43 -0800 From: "JDC Tech Tips" To: alexp@mit.edu Message-Id: <28542086331453377@hermes.sun.com> Subject: Core Java Technologies Tech Tips, Jan. 22, 2003 (Reading Files from JARs, Into to JMX) Mime-Version: 1.0 Content-Type: text/html; charset=ISO-8859-1 Content-Transfer-Encoding: 7bit X-Mailer: SunMail 1.0 Core Java Technologies Technical Tips
image
image
Core Java Technologies Technical Tips
image
   View this issue as simple text January 22, 2003    

In this Issue

Welcome to the Core JavaTM Technologies Tech Tips, January 22, 2003. Here you'll get tips on using core Java technologies and APIs, such as those in Java 2 Platform, Standard Edition (J2SETM).

This issue covers:

Reading files from Java Archives (JARs)
Getting Started with the JavaTM Management Extensions (JMXTM)

These tips were developed using Java 2 SDK, Standard Edition, v 1.4.

This issue of the Core Java Technologies Tech Tips is written by John Zukowski, president of JZ Ventures, Inc.

Pixel
Pixel

READING FILES FROM JAVA ARCHIVES (JARS)

Java archive (JAR) files are the standard way of packaging Java technology-based solutions. They allow a developer to package all relevant content (.class, image, sound, and support files) in a single file. The JAR format supports compression, authentication, and versioning, among many other features.

Getting files out of JAR files can be a tricky task, but it doesn't have to be. This tip shows you how to get a file out of a JAR file, by first getting a directory of files in the JAR file, and then pulling out a specific one.

If you are familiar with the popular ZIP format, JAR files aren't much different. JAR files provide a way of packaging multiple files into one, where each file may be compressed separately. What the JAR file adds is something called a manifest which allows a developer to provide additional information about the content. For example, the manifest can indicate which file in the JAR file to run to start an application, or the version of a library.

The Java 2 SDK, Standard Edition provides a jar tool that allows you to read and write JAR files from the console. However there might be times when you need to read and write JAR files from within your programs. (This tip will only cover reading JAR files from within a program.) The good news is that you can do this, and you don't have to worry about the decompression, because the library handles it for you. The classes you need are in the java.util.jar package. The main class here is JarFile which is a reference to the .jar file itself. Each individual file within the bigger file is referenced by a JarEntry.

To get started, you create a JarFile instance by passing the location to the constructor. This could be in the form of a String or a File:

    JarFile jarFile = new JarFile("thefile.jar");

or

    File file = new File("thefile.jar");
    JarFile jarFile = new JarFile(file);

There are other constructors for authentication support and marking the file for deletion. However those constructors will not be covered here.

After you have a reference to the JAR file, you can read the directory of its contents. The entries method of JarFile returns an Enumeration of all the entries. From each entry, you can then get its attributes from the manifest file, any certificate information, and any other information specific to the entry such as its name or size.

  Enumeration enum = jarFile.entries();
  while (enum.hasMoreElements()) {
    process(enum.nextElement());
  }

As previously mentioned, each individual entry is a JarEntry. This class has methods such as getName, getSize, and getCompressedSize.

Let's illustrate how to use these features in a program. The following program displays the name, size, and compressed size of the contents of a JAR file you specify. (This is similar to what the jar command does when you specify it with the "t" and "v" options.)

   import java.io.*;
   import java.util.*;
   import java.util.jar.*;

   public class JarDir {
     public static void main (String args[]) 
         throws IOException {
       if (args.length != 1) {
         System.out.println(
            "Please provide a JAR filename");
         System.exit(-1);
       }
       JarFile jarFile = new JarFile(args[0]);
       Enumeration enum = jarFile.entries();
       while (enum.hasMoreElements()) {
         process(enum.nextElement());
       }
     }

     private static void process(Object obj) {
       JarEntry entry = (JarEntry)obj;
       String name = entry.getName();
       long size = entry.getSize();
       long compressedSize = entry.getCompressedSize();
       System.out.println(
           name + "\t" + size + "\t" + compressedSize);
     }
   }

If you run the JarDir program with the jce.jar file that comes with J2SE 1.4.1, you should see output that looks something like this (with more files shown where the ... is):

META-INF/MANIFEST.MF    5315    1910
META-INF/4JCEJARS.SF    5368    1958
META-INF/4JCEJARS.DSA   2207    1503
META-INF/       0       2
javax/  0       0
javax/crypto/   0       0
javax/crypto/interfaces/        0       0
javax/crypto/interfaces/DHKey.class     209     185
javax/crypto/interfaces/DHPublicKey.class       265     215
javax/crypto/interfaces/DHPrivateKey.class      267     215
javax/crypto/interfaces/PBEKey.class    268     224
javax/crypto/SecretKey.class    167     155
...

Notice the META-INF lines at the start of the output. This is the manifest and security certificate information. The entries with a 0 size are not files, but rather directories.

To actually read a specific file from a JAR file, you must get the InputStream for the entry. This is different than the JarEntry. That's because the JarEntry only contains information about the entry, not the actual contents of that entry. This is similar to the distinction between File and FileInputStream. Accessing File never opens the file, it just reads the information about it from the directory. Here's how to get the InputStream for the entry:

   InputStream input = jarFile.getInputStream(entry);

After you have an input stream, you can just read it like any other stream. In the case of a text stream, remember to use a Reader to get characters from the stream. For byte-oriented streams such as image files, just read directly.

The following program demonstrates reading from a JAR file. Call the program with the name of a JAR file followed by the file name to read. The file to be read must have a text file type.

   import java.io.*;
   import java.util.jar.*;

   public class JarRead {
     public static void main (String args[]) 
         throws IOException {
       if (args.length != 2) {
         System.out.println(
           "Please provide a JAR filename and file to read");
         System.exit(-1);
       }
       JarFile jarFile = new JarFile(args[0]);
       JarEntry entry = jarFile.getJarEntry(args[1]);
       InputStream input = jarFile.getInputStream(entry);
       process(input);
     }

     private static void process(InputStream input) 
         throws IOException {
       InputStreamReader isr = 
      new InputStreamReader(input);
       BufferedReader reader = new BufferedReader(isr);
       String line;
       while ((line = reader.readLine()) != null) {
         System.out.println(line);
       }
       reader.close();
     }
   }

Suppose you had a text file named spider.txt in a JAR file named myfiles.jar. Suppose too that spider.txt contained the following text:

   The itsy bitsy spider 
   Ran up the water spout
   Down came the rain and
   Washed the spider out 

You could display the contents of the text file from the JAR file like this:

   java JarRead myfiles.jar spider.txt   

For more about JAR files, see the JAR file specification.

Pixel
Pixel

GETTING STARTED WITH THE JAVA MANAGEMENT EXTENSIONS (JMX)

The Java Management Extensions (JMX) offers a framework for the management and monitoring of resources. It is not yet a standard part of the Java 2 Standard Edition, but the reference implementation works with J2SE 1.4 and is a part of the JavaTM 2 Enterprise Edition (J2EETM) 1.4 specification currently in beta.

The purpose of JMX is to manage and monitor resources. Resources can be physical, for example small network devices, or logical, for example installed applications. You define the pieces in objects called MBeans. These objects contain the managable resources. The resources could be something like JavaBean component properties. The MBeans are then exposed to JMX agents. The agents know how to get the state of the resources through MBeans, and are permitted to change them if exposed in a writable fashion. The agents are then accessible through a distributed layer, typically through a client using HTML or the Simple Network Management Protocol (SNMP). This allows you to control the settings of the devices without having to run to equipment closets or create custom management tools for applications.

This tip isn't meant to present a complete discussion of the how and why of the JMX architecture. Instead, it's designed to give you a quick, "hands-on" information about using JMX over the lifetime of a program. To get started, you need the binaries for the JMX version 1.2 reference implementation. You can download the binaries from the JMX home page. There is just one version for all platforms. Unpack the files into a working directory.

There are two JAR files in the download: lib/jmxri.jar and lib/jmxtools.jar. The jmxri.jar file represents the reference implementation. These are the standard JMX classes, and can be found in the javax.management package. The jmxtools.jar file is the JMX toolkit. These represent unsupported classes that Sun provides for development. Both of these JAR files will need to be in your CLASSPATH. For simplicity sake, just copy these to the jre/lib/ext directory under the installation directory for your Java Runtime Environment (JRE).

If your JRE directory is C:\j2sdk1.4.1\jre\lib\ext, then the copy command would be as follows:

copy lib\*.jar \j2sdk1.4.1\jre\lib\ext

(If you don't want these available after this tip, remember to remove the files from the jre\lib\ext directory when done.)

Next, let's define an interface for the operations you want the MBean to perform. Typically, custom interfaces are used, but aren't required. However, they make things simpler as you move into more complicated JMX behaviors, so let's use one here. (If you don't create a custom interface, you must implement javax.management.DynamicMBean.) The MBean will have one read-write resource to manage (a message). It will also have a read-only counter of the number of times that resource changed, and a reset mechanism to reinitialize the counter value. (Imagine counting the number of IP addresses a network box had over the course of a week.) Here's the definition of the interface:

   public interface HelloMBean {
     public String getMessage();
     public void setMessage(String message);
     public int getChangeCount();
     public void resetCounter();
   }

The first three methods look like they access JavaBean component properties (called "attributes" in JMX). In fact, they do access properties. The last method is simply a random method thrown in to show that everything defined in the interface doesn't have to be property-related. JMX supports executing any method, not just the getting and setting of properties.

The implementation of this interface is rather simple:

   public class Hello implements HelloMBean {
     private String message;
     private int changeCount;
  
     public String getMessage() {
       return message;
     }
  
     public void setMessage(String message){
       this.message = message;
       changeCount++;
     }
  
     public int getChangeCount() {
       return changeCount;
     }
  
     public void resetCounter() {
       changeCount = 0;
     }
   }

Notice the naming convention of the concrete class. The interface name follows the name of [ClassName]MBean, so the class is just [ClassName], replacing [ClassName] with the specific name. This is similar to the naming convention of getter and setter methods for attributes.

So far, you haven't seen any JMX-specific code, but that is about to change. The MBean Server needs to communicate with the MBeans. That communication is done by registering various agents with the server. Then, anyone using an appropriate client interface can execute any of the methods of the exposed interface, in this case, HelloMBean.

The MBean server is aptly named MBeanServer. You get the server from the MBeanServerFactory. You can either name your server or call createMBeanServer with no arguments to get the default domain.

   MBeanServer server = 
      MBeanServerFactory.createMBeanServer();

Next, you register your MBeans with the server. Each bean is registered in the form domain:key1=XXX1,key2=XXX2, where key1 is an attribute name and XXX is the associated value (and domain is replaced by the domain name). Think of it like naming an instance of a class. Before the :, the domain names a unique namespace for the instances. Typically, these are named similar to packages to ensure uniqueness, using reverse DNS names (for example, com.sun.java).

So, if you wanted to create and register two MBeans named hello1 and hello2, of type Hello, you could use the following code:

   HelloMBean hello1 = new Hello();
   ObjectName helloObjectName = new ObjectName(
     "HelloServer:type=Hello,name=hello1");
   server.registerMBean(hello, helloObjectName);
   HelloMBean hello2 = new Hello();
   ObjectName helloObjectName2 = new ObjectName(
     "HelloServer:type=Hello,name=hello2");
   server.registerMBean(hello2, helloObjectName2);

For simplicity, the domain name here is just HelloServer, because there is no chance of conflicts in the example.

A program with this much in it would be sufficient to support JMX. However, you wouldn't be able to see much. To see something more, you need a client interface. However instead of providing your own client interface, use the jmxtools.jar part of the download.

Within jmxtools.jar is the HtmlAdaptorServer. This gives you an HTML view into the MBean server. You need to create and register the server just like a regular MBean. However there is one exception: you have to tell the server what port to listen on. This example uses port 8082. If this port is not available for you, choose another and make the necessary code changes. Then, you can connect the HTML adaptor to the MBean server and access the MBeans.

   HtmlAdaptorServer adapterServer = 
     new HtmlAdaptorServer();
   ObjectName adapterObjectName = new ObjectName(
      "HelloServer:name=htmladapter,port=8082");
   adapterServer.setPort(8082);
   server.registerMBean(
      adapterServer, adapterObjectName);
   adapterServer.start();

The complete server program follows, with explicit catching of all the possible exceptions:

 import com.sun.jdmk.comm.*;
 import javax.management.*;

 public class HelloAgent {
    public static void main(String args[]) {
     MBeanServer server = 
       MBeanServerFactory.createMBeanServer();
     HtmlAdaptorServer adaptorServer = 
        new HtmlAdaptorServer();
     HelloMBean hello1 = new Hello();
     HelloMBean hello2 = new Hello();
     try {
       ObjectName helloObjectName1 = new ObjectName(
         "HelloServer:type=Hello,name=hello1");
       server.registerMBean(hello1, helloObjectName1);
       ObjectName helloObjectName2 = new ObjectName(
         "HelloServer:type=Hello,name=hello2");
       server.registerMBean(hello2, helloObjectName2);
       ObjectName adaptorObjectName = new ObjectName(
          "HelloServer:type=htmladaptor,port=8082");
       adaptorServer.setPort(8082);
       server.registerMBean(
          adaptorServer, adaptorObjectName);
       adaptorServer.start();
     } catch (MalformedObjectNameException e) {
       System.out.println("Bad object name");
       e.printStackTrace();
     } catch (InstanceAlreadyExistsException e) {
       System.out.println("Already exists");
       e.printStackTrace();
     } catch (MBeanRegistrationException e) {
       System.out.println("Registration problems");
       e.printStackTrace();
     } catch (NotCompliantMBeanException e) {
       System.out.println("Registration problems");
       e.printStackTrace();
     }
   }
 }

You should now compile the three classes and run the HelloAgent program:

javac HelloMBean.java Hello.java HelloAgent.java
java HelloAgent

The HelloAgent program will not return. It is running the application, including maintaining the current state of your MBeans. To access the server you can use any HTML client to connect to the appropriate port. To manage the agent, point your browser to http://localhost:8082/.

Your browser should display a screen that lists the HelloServer and its three MBeans (two Hello beans and the HTML adaptor):

  • hello1
  • hello2
  • htmladapter

Clicking on one of the beans allows you to change or monitor its settings.

To demonstrate, try changing the message property and watch the change count increase.

Also clear the change count.

Clicking on the MBeanServerDelegate bean will show you information about the server.

There is much more to JMX than what this tip covered. This tip was designed to get you started so you can see how you might configure and monitor your Java solutions when they are deployed. Instead of developing custom solutions for exposing the management interface to the system configuration, simply use JMX.

For more information about JMX, and for access to other JMX resources, visit the JMX home page.

Pixel
Pixel

Reader Feedback

  Very worth reading    Worth reading    Not worth reading 

If you have other comments or ideas for future technical tips, please type them here:

 

Have a question about JavaTM programming? Use Java Online Support.

Pixel
Pixel
Pixel

IMPORTANT: Please read our Terms of Use, Privacy, and Licensing policies:
http://www.sun.com/share/text/termsofuse.html
http://www.sun.com/privacy/
http://developer.java.sun.com/berkeley_license.html


Comments? Send your feedback on the Core JavaTM Technologies Tech Tips to: jdc-webmaster@sun.com

Subscribe to other Java developer Tech Tips:

- Enterprise Java Technologies Tech Tips. Get tips on using enterprise Java technologies and APIs, such as those in the Java 2 Platform, Enterprise Edition (J2EETM).
- Wireless Developer Tech Tips. Get tips on using wireless Java technologies and APIs, such as those in the Java 2 Platform, Micro Edition (J2METM).

To subscribe to these and other JDC publications:
- Go to the JDC Newsletters and Publications page, choose the newsletters you want to subscribe to and click "Update".
- To unsubscribe, go to the subscriptions page, uncheck the appropriate checkbox, and click "Update".


ARCHIVES: You'll find the Core Java Technologies Tech Tips archives at:
http://java.sun.com/jdc/TechTips/index.html


Copyright 2003 Sun Microsystems, Inc. All rights reserved.
901 San Antonio Road, Palo Alto, California 94303 USA.


This document is protected by copyright. For more information, see:
http://java.sun.com/jdc/copyright.html


Sun, Sun Microsystems, Java, Java Developer Connection, JMX, J2SE, J2EE, and J2ME are trademarks or registered trademarks of Sun Microsystems, Inc. in the United States and other countries.

Sun Microsystems, Inc.
Please unsubscribe me from this newsletter.