This document was uploaded by user and they confirmed that they have the permission to share
it. If you are author or own the copyright of this book, please report to us by using this DMCA
report form. Report DMCA
USES OF REFLECTION........................................................................................50 DRAWBACKS OF REFLECTION.................................................................................50 TRAIL LESSONS..............................................................................................51 SECURITY
58
RMI
59
ADVANTAGES OF DYNAMIC CODE LOADING..................................................................60 REMOTE INTERFACES, OBJECTS, AND METHODS.............................................................61 CREATING DISTRIBUTED APPLICATIONS BY USING RMI.....................................................62 DESIGNING AND IMPLEMENTING THE APPLICATION COMPONENTS.............................................62 COMPILING SOURCES........................................................................................63 MAKING CLASSES NETWORK ACCESSIBLE....................................................................63 STARTING THE APPLICATION.................................................................................63 BUILDING A GENERIC COMPUTE ENGINE.....................................................................63 DECLARING THE REMOTE INTERFACES BEING IMPLEMENTED..................................................69 DEFINING THE CONSTRUCTOR FOR THE REMOTE OBJECT.....................................................69 PROVIDING IMPLEMENTATIONS FOR EACH REMOTE METHOD.................................................70 PASSING OBJECTS IN RMI..................................................................................70 IMPLEMENTING THE SERVER'S MAIN METHOD................................................................71 CREATING AND INSTALLING A SECURITY MANAGER..........................................................72 MAKING THE REMOTE OBJECT AVAILABLE TO CLIENTS......................................................72
2 BUILDING A JAR FILE OF INTERFACE CLASSES.............................................................82 BUILDING THE SERVER CLASSES.............................................................................84 BUILDING THE CLIENT CLASSES.............................................................................85 A NOTE ABOUT SECURITY...................................................................................86 STARTING THE SERVER.......................................................................................87 STARTING THE CLIENT.......................................................................................89 SWINGS
91
TOP-LEVEL CONTAINERS AND CONTAINMENT HIERARCHIES..................................................97 ADDING COMPONENTS TO THE CONTENT PANE..............................................................97 ADDING A MENU BAR.......................................................................................98 THE ROOT PANE.............................................................................................99 JAAS
100
INTERNATIONALIZATION
100
1. CREATE THE PROPERTIES FILES........................................................................103 2. DEFINE THE LOCALE....................................................................................104 3. CREATE A RESOURCEBUNDLE...........................................................................105 4. FETCH THE TEXT FROM THE RESOURCEBUNDLE.........................................................105 CONCLUSION...............................................................................................106 JUNIT
Sources: http://java.sun.com/docs/books/tutorial/collections/interfaces/co llection.html Lesson: Introduction to Collections
9
A collection — sometimes called a container — is simply an object that groups multiple elements into a single unit. Collections are used to store, retrieve, manipulate, and communicate aggregate data. Typically, they represent data items that form a natural group, such as a poker hand (a collection of cards), a mail folder (a collection of letters), or a telephone directory (a mapping of names to phone numbers). If you've used the Java programming language — or just about any other programming language — you're already familiar with collections. Collection implementations in earlier (pre-1.2) versions of the Java platform included Vector, Hashtable, and array. However, those earlier versions did not contain a collections framework. What Is a Collections Framework? A collections framework is a unified architecture for representing and manipulating collections. All collections frameworks contain the following: * Interfaces: These are abstract data types that represent collections. Interfaces allow collections to be manipulated independently of the details of their representation. In objectoriented languages, interfaces generally form a hierarchy. * Implementations: These are the concrete implementations of the collection interfaces. In essence, they are reusable data structures. * Algorithms: These are the methods that perform useful computations, such as searching and sorting, on objects that implement collection interfaces. The algorithms are said to be polymorphic: that is, the same method can be used on many different implementations of the appropriate collection interface. In essence, algorithms are reusable functionality.
10
Apart from the Java Collections Framework, the best-known examples of collections frameworks are the C++ Standard Template Library (STL) and Smalltalk's collection hierarchy. Historically, collections frameworks have been quite complex, which gave them a reputation for having a steep learning curve. We believe that the Java Collections Framework breaks with this tradition, as you will learn for yourself in this chapter. Benefits of the Java Collections Framework The Java Collections Framework provides the following benefits: * Reduces programming effort: By providing useful data structures and algorithms, the Collections Framework frees you to concentrate on the important parts of your program rather than on the low-level "plumbing" required to make it work. By facilitating interoperability among unrelated APIs, the Java Collections Framework frees you from writing adapter objects or conversion code to connect APIs. * Increases program speed and quality: This Collections Framework provides high-performance, high-quality implementations of useful data structures and algorithms. The various implementations of each interface are interchangeable, so programs can be easily tuned by switching collection implementations. Because you're freed from the drudgery of writing your own data structures, you'll have more time to devote to improving programs' quality and performance. * Allows interoperability among unrelated APIs: The collection interfaces are the vernacular by which APIs pass collections back and forth. If my network administration API furnishes a collection of node names and if your GUI toolkit expects a collection of column headings, our APIs will
11
interoperate seamlessly, even though they were written independently. * Reduces effort to learn and to use new APIs: Many APIs naturally take collections on input and furnish them as output. In the past, each such API had a small sub-API devoted to manipulating its collections. There was little consistency among these ad hoc collections sub-APIs, so you had to learn each one from scratch, and it was easy to make mistakes when using them. With the advent of standard collection interfaces, the problem went away. * Reduces effort to design new APIs: This is the flip side of the previous advantage. Designers and implementers don't have to reinvent the wheel each time they create an API that relies on collections; instead, they can use standard collection interfaces. * Fosters software reuse: New data structures that conform to the standard collection interfaces are by nature reusable. The same goes for new algorithms that operate on objects that implement these interfaces. Lesson: Interfaces The core collection interfaces encapsulate different types of collections, which are shown in the figure below. These interfaces allow collections to be manipulated independently of the details of their representation. Core collection interfaces are the foundation of the Java Collections Framework. As you can see in the following figure, the core collection interfaces form a hierarchy. Two interface trees, one starting with Collection and including Set, SortedSet, List, and Queue, and the other starting with Map and including SortedMap. The core collection interfaces.
12
A Set is a special kind of Collection, a SortedSet is a special kind of Set, and so forth. Note also that the hierarchy consists of two distinct trees — a Map is not a true Collection. Note that all the core collection interfaces are generic. For example, this is the declaration of the Collection interface. public interface Collection<E>... The <E> syntax tells you that the interface is generic. When you declare a Collection instance you can and should specify the type of object contained in the collection. Specifying the type allows the compiler to verify (at compile-time) that the type of object you put into the collection is correct, thus reducing errors at runtime. For information on generic types, see the Generics lesson. When you understand how to use these interfaces, you will know most of what there is to know about the Java Collections Framework. This chapter discusses general guidelines for effective use of the interfaces, including when to use which interface. You'll also learn programming idioms for each interface to help you get the most out of it. To keep the number of core collection interfaces manageable, the Java platform doesn't provide separate interfaces for each variant of each collection type. (Such variants might include immutable, fixed-size, and append-only.) Instead, the modification operations in each interface are designated optional — a given implementation may elect not to support all operations. If an unsupported operation is invoked, a collection throws an UnsupportedOperationException. Implementations are responsible for documenting which of the optional operations they support. All of the Java platform's general-purpose implementations support all of the optional operations.
13
The following list describes the core collection interfaces: * Collection — the root of the collection hierarchy. A collection represents a group of objects known as its elements. The Collection interface is the least common denominator that all collections implement and is used to pass collections around and to manipulate them when maximum generality is desired. Some types of collections allow duplicate elements, and others do not. Some are ordered and others are unordered. The Java platform doesn't provide any direct implementations of this interface but provides implementations of more specific subinterfaces, such as Set and List. Also see The Collection Interface section. * Set — a collection that cannot contain duplicate elements. This interface models the mathematical set abstraction and is used to represent sets, such as the cards comprising a poker hand, the courses making up a student's schedule, or the processes running on a machine. See also The Set Interface section. * List — an ordered collection (sometimes called a sequence). Lists can contain duplicate elements. The user of a List generally has precise control over where in the list each element is inserted and can access elements by their integer index (position). If you've used Vector, you're familiar with the general flavor of List. Also see The List Interface section. * Queue — a collection used to hold multiple elements prior to processing. Besides basic Collection operations, a Queue provides additional insertion, extraction, and inspection operations. Queues typically, but do not necessarily, order elements in a FIFO (first-in, first-out) manner. Among the exceptions are
14
priority queues, which order elements according to a supplied comparator or the elements' natural ordering. Whatever the ordering used, the head of the queue is the element that would be removed by a call to remove or poll. In a FIFO queue, all new elements are inserted at the tail of the queue. Other kinds of queues may use different placement rules. Every Queue implementation must specify its ordering properties. Also see The Queue Interface section. * Map — an object that maps keys to values. A Map cannot contain duplicate keys; each key can map to at most one value. If you've used Hashtable, you're already familiar with the basics of Map. Also see The Map Interface section. The last two core collection interfaces are merely sorted versions of Set and Map: * SortedSet — a Set that maintains its elements in ascending order. Several additional operations are provided to take advantage of the ordering. Sorted sets are used for naturally ordered sets, such as word lists and membership rolls. Also see The SortedSet Interface section. * SortedMap — a Map that maintains its mappings in ascending key order. This is the Map analog of SortedSet. Sorted maps are used for naturally ordered collections of key/value pairs, such as dictionaries and telephone directories. Also see The SortedMap Interface section. To understand how the sorted interfaces maintain the order of their elements, see the Object Ordering section. The Collection Interface
15
A Collection represents a group of objects known as its elements. The Collection interface is used to pass around collections of objects where maximum generality is desired. For example, by convention all general-purpose collection implementations have a constructor that takes a Collection argument. This constructor, known as a conversion constructor, initializes the new collection to contain all of the elements in the specified collection, whatever the given collection's subinterface or implementation type. In other words, it allows you to convert the collection's type. Suppose, for example, that you have a Collection<String> c, which may be a List, a Set, or another kind of Collection. This idiom creates a new ArrayList (an implementation of the List interface), initially containing all the elements in c. List<String> list = new ArrayList<String>(c); The following shows the Collection interface. public interface Collection<E> extends Iterable<E> { // Basic operations int size(); boolean isEmpty(); boolean contains(Object element); boolean add(E element); //optional boolean remove(Object element); //optional Iterator<E> iterator(); // Bulk operations boolean containsAll(Collection> c); boolean addAll(Collection extends E> c); //optional boolean removeAll(Collection> c); //optional boolean retainAll(Collection> c); //optional void clear(); //optional
The interface does about what you'd expect given that a Collection represents a group of objects. The interface has methods to tell you how many elements are in the collection (size, isEmpty), to check whether a given object is in the collection (contains), to add and remove an element from the collection (add, remove), and to provide an iterator over the collection (iterator). The add method is defined generally enough so that it makes sense for collections that allow duplicates as well as those that don't. It guarantees that the Collection will contain the specified element after the call completes, and returns true if the Collection changes as a result of the call. Similarly, the remove method is designed to remove a single instance of the specified element from the Collection, assuming that it contains the element to start with, and to return true if the Collection was modified as a result. Traversing Collections There are two ways to traverse collections: (1) with the foreach construct and (2) by using Iterators. for-each Construct The for-each construct allows you to concisely traverse a collection or array using a for loop — see The for Statement. The following code uses the for-each construct to print out each element of a collection on a separate line. for (Object o : collection)
17
System.out.println(o); Iterators An Iterator is an object that enables you to traverse through a collection and to remove elements from the collection selectively, if desired. You get an Iterator for a collection by calling its iterator method. The following is the Iterator interface. public interface Iterator<E> { boolean hasNext(); E next(); void remove(); //optional } The hasNext method returns true if the iteration has more elements, and the next method returns the next element in the iteration. The remove method removes the last element that was returned by next from the underlying Collection. The remove method may be called only once per call to next and throws an exception if this rule is violated. Note that Iterator.remove is the only safe way to modify a collection during iteration; the behavior is unspecified if the underlying collection is modified in any other way while the iteration is in progress. Use Iterator instead of the for-each construct when you need to: * Remove the current element. The for-each construct hides the iterator, so you cannot call remove. Therefore, the foreach construct is not usable for filtering. * Iterate over multiple collections in parallel.
18
The following method shows you how to use an Iterator to filter an arbitrary Collection — that is, traverse the collection removing specific elements. static void filter(Collection> c) { for (Iterator> it = c.iterator(); it.hasNext(); ) if (!cond(it.next())) it.remove(); } This simple piece of code is polymorphic, which means that it works for any Collection regardless of implementation. This example demonstrates how easy it is to write a polymorphic algorithm using the Java Collections Framework. Collection Interface Bulk Operations Bulk operations perform an operation on an entire Collection. You could implement these shorthand operations using the basic operations, though in most cases such implementations would be less efficient. The following are the bulk operations: * containsAll — returns true if the target Collection contains all of the elements in the specified Collection. * addAll — adds all of the elements in the specified Collection to the target Collection. * removeAll — removes from the target Collection all of its elements that are also contained in the specified Collection. * retainAll — removes from the target Collection all its elements that are not also contained in the specified Collection. That is, it retains only those elements in the target Collection that are also contained in the specified Collection. * clear — removes all elements from the Collection.
19
The addAll, removeAll, and retainAll methods all return true if the target Collection was modified in the process of executing the operation. As a simple example of the power of bulk operations, consider the following idiom to remove all instances of a specified element, e, from a Collection, c. c.removeAll(Collections.singleton(e)); More specifically, suppose you want to remove all of the null elements from a Collection. c.removeAll(Collections.singleton(null)); This idiom uses Collections.singleton, which is a static factory method that returns an immutable Set containing only the specified element. Collection Interface Array Operations The toArray methods are provided as a bridge between collections and older APIs that expect arrays on input. The array operations allow the contents of a Collection to be translated into an array. The simple form with no arguments creates a new array of Object. The more complex form allows the caller to provide an array or to choose the runtime type of the output array. For example, suppose that c is a Collection. The following snippet dumps the contents of c into a newly allocated array of Object whose length is identical to the number of elements in c. Object[] a = c.toArray();
20
Suppose that c is known to contain only strings (perhaps because c is of type Collection<String>). The following snippet dumps the contents of c into a newly allocated array of String whose length is identical to the number of elements in c. String[] a = c.toArray(new String[0]); JDBC
Introduces an API for connectivity between the Java applications and a wide range of databases and a data sources. JDBC(TM) Database Access The JDBC™ API was designed to keep simple things simple. This means that the JDBC makes everyday database tasks easy. This trail walks you through examples of using JDBC to execute common SQL statements, and perform other objectives common to database applications. This trail is divided into these lessons: JDBC Introduction Lists JDBC features, describes JDBC Architecture and reviews SQL commands and Relational Database concepts. JDBC Basics covers the JDBC API, which is included in the Java™ SE 6 release. By the end of the first lesson, you will know how to use the basic JDBC API to create tables, insert values into them, query the tables, retrieve the results of the queries, and update the tables. In this process, you will learn how to use simple statements and prepared statements, and you will see an example of a stored procedure. You will also learn how to perform transactions and how to catch exceptions and warnings.
JDBC Introduction
21
The JDBC API is a Java API that can access any kind of tabular data, especially data stored in a Relational Database. JDBC helps you to write java applications that manage these three programming activities: 1. Connect to a data source, like a database 2. Send queries and update statements to the database 3. Retrieve and process the results received from the database in answer to your query The following simple code fragment gives a simple example of these three steps: 4. Connection con = DriverManager.getConnection 5. ( "jdbc:myDriver:wombat", "myLogin","myPassword"); 6. 7. Statement stmt = con.createStatement(); 8. ResultSet rs = stmt.executeQuery("SELECT a, b, c FROM Table1"); 9. while (rs.next()) { 10. int x = rs.getInt("a"); 11. String s = rs.getString("b"); 12. float f = rs.getFloat("c"); 13. }
This short code fragment instantiates a DriverManager object to connect to a database driver and log into the database, instantiates a Statement object that carries your SQL language query to the database; instantiates a ResultSet object that retrieves the results of your query, and executes a simple while loop, which retrieves and displays those results. It's that simple.
JDBC Product Components JDBC includes four components:
22 1.
The JDBC API — The JDBC™ API provides programmatic access to relational data from the Java™ programming language. Using the JDBC API, applications can execute SQL statements, retrieve results, and propagate changes back to an underlying data source. The JDBC API can also interact with multiple data sources in a distributed, heterogeneous environment. The JDBC API is part of the Java platform, which includes the Java™ Standard Edition (Java™ SE ) and the Java™ Enterprise Edition (Java™ EE). The JDBC 4.0 API is divided into two packages: java.sql and javax.sql. Both packages are included in the Java SE and Java EE platforms.
2.
JDBC Driver Manager — The JDBC DriverManager class defines objects which can connect Java applications to a JDBC driver. DriverManager has traditionally been the backbone of the JDBC architecture. It is quite small and simple. The Standard Extension packages javax.naming and javax.sql let you use a DataSource object registered with a Java Naming and Directory Interface™ (JNDI) naming service to establish a connection with a data source. You can use either connecting mechanism, but using a DataSource object is recommended whenever possible.
3.
JDBC Test Suite —
23
The JDBC driver test suite helps you to determine that JDBC drivers will run your program. These tests are not comprehensive or exhaustive, but they do exercise many of the important features in the JDBC API.
4.
JDBC-ODBC Bridge — The Java Software bridge provides JDBC access via ODBC drivers. Note that you need to load ODBC binary code onto each client machine that uses this driver. As a result, the ODBC driver is most appropriate on a corporate network where client installations are not a major problem, or for application server code written in Java in a three-tier architecture.
This Trail uses the first two of these these four JDBC components to connect to a database and then build a java program that uses SQL commands to communicate with a test Relational Database. The last two components are used in specialized environments to test web applications, or to communicate with ODBC-aware DBMSs.
JDBC Architecture
Two-tier and Three-tier Processing Models The JDBC API supports both two-tier and three-tier processing models for database access. Figure 1: Two-tier Architecture for Data Access.
24
In the two-tier model, a Java applet or application talks directly to the data source. This requires a JDBC driver that can communicate with the particular data source being accessed. A user's commands are delivered to the database or other data source, and the results of those statements are sent back to the user. The data source may be located on another machine to which the user is connected via a network. This is referred to as a client/server configuration, with the user's machine as the client, and the machine housing the data source as the server. The network can be an intranet, which, for example, connects employees within a corporation, or it can be the Internet. In the three-tier model, commands are sent to a "middle tier" of services, which then sends the commands to the data source. The data source processes the commands and sends the results back to the middle tier, which then sends them to the user. MIS directors find the three-tier model very attractive because the middle tier makes it possible to maintain control over access and the kinds of updates that can be made to corporate data. Another advantage is that it simplifies the deployment of applications. Finally, in many cases, the three-tier architecture can provide performance advantages. Figure 2: Three-tier Architecture for Data Access.
25
Until recently, the middle tier has often been written in languages such as C or C++, which offer fast performance. However, with the introduction of optimizing compilers that translate Java bytecode into efficient machine-specific code and technologies such as Enterprise JavaBeans™, the Java platform is fast becoming the standard platform for middle-tier development. This is a big plus, making it possible to take advantage of Java's robustness, multithreading, and security features. With enterprises increasingly using the Java programming language for writing server code, the JDBC API is being used more and more in the middle tier of a three-tier architecture. Some of the features that make JDBC a server technology are its support for connection pooling, distributed transactions, and disconnected rowsets. The JDBC API is also what allows access to a data source from a Java middle tier.
A Relational Database Overview A database is a means of storing information in such a way that information can be retrieved from it. In simplest terms, a relational database is one that presents information in tables with rows and columns. A table is referred to as a relation in the sense that it is a collection of objects of the same type (rows). Data in a table can be related according to common keys or concepts, and the ability to retrieve related data from a table is
26
the basis for the term relational database. A Database Management System (DBMS) handles the way data is stored, maintained, and retrieved. In the case of a relational database, a Relational Database Management System (RDBMS) performs these tasks. DBMS as used in this book is a general term that includes RDBMS.
Integrity Rules Relational tables follow certain integrity rules to ensure that the data they contain stay accurate and are always accessible. First, the rows in a relational table should all be distinct. If there are duplicate rows, there can be problems resolving which of two possible selections is the correct one. For most DBMSs, the user can specify that duplicate rows are not allowed, and if that is done, the DBMS will prevent the addition of any rows that duplicate an existing row. A second integrity rule of the traditional relational model is that column values must not be repeating groups or arrays. A third aspect of data integrity involves the concept of a null value. A database takes care of situations where data may not be available by using a null value to indicate that a value is missing. It does not equate to a blank or zero. A blank is considered equal to another blank, a zero is equal to another zero, but two null values are not considered equal. When each row in a table is different, it is possible to use one or more columns to identify a particular row. This unique column or group of columns is called a primary key. Any column that is part of a primary key cannot be null; if it were, the primary key containing it would no longer be a complete identifier. This rule is referred to as entity integrity. Table 1.2 illustrates some of these relational database concepts. It has five columns and six rows, with each row representing a different employee.
First_name Axel Arvid Jonas Florence Sean Elizabeth
Last_Name Date_of_Birth Washington 28-Aug-43 Sharma 24-Nov-54 Ginsberg 01-Jan-69 Wojokowski 04-Jul-71 Washington 21-Sep-66 Yamaguchi 24-Dec-59
Car_Number 5 null null 12 null null
The primary key for this table would generally be the employee number because each one is guaranteed to be different. (A number is also more efficient than a string for making comparisons.) It would also be possible to use First_Name and Last_Name because the combination of the two also identifies just one row in our sample database. Using the last name alone would not work because there are two employees with the last name of "Washington." In this particular case the first names are all different, so one could conceivably use that column as a primary key, but it is best to avoid using a column where duplicates could occur. If Elizabeth Taylor gets a job at this company and the primary key is First_Name, the RDBMS will not allow her name to be added (if it has been specified that no duplicates are permitted). Because there is already an Elizabeth in the table, adding a second one would make the primary key useless as a way of identifying just one row. Note that although using First_Name and Last_Name is a unique composite key for this example, it might not be unique in a larger database. Note also that Table 1.2 assumes that there can be only one car per employee. SELECT
Statements
SQL is a language designed to be used with relational databases. There is a set of basic SQL commands that is considered
28
standard and is used by all RDBMSs. For example, all RDBMSs use the SELECT statement. A SELECT statement, also called a query, is used to get information from a table. It specifies one or more column headings, one or more tables from which to select, and some criteria for selection. The RDBMS returns rows of the column entries that satisfy the stated requirements. A SELECT statement such as the following will fetch the first and last names of employees who have company cars: SELECT First_Name, Last_Name FROM Employees WHERE Car_Number IS NOT NULL
The result set (the set of rows that satisfy the requirement of not having null in the Car_Number column) follows. The first name and last name are printed for each row that satisfies the requirement because the SELECT statement (the first line) specifies the columns First_Name and Last_Name. The FROM clause (the second line) gives the table from which the columns will be selected. FIRST_NAME ---------Axel Florence
LAST_NAME ----------Washington Wojokowski
The following code produces a result set that includes the whole table because it asks for all of the columns in the table Employees with no restrictions (no WHERE clause). Note that SELECT * means "SELECT all columns." SELECT * FROM Employees
29
WHERE
Clauses
The WHERE clause in a SELECT statement provides the criteria for selecting values. For example, in the following code fragment, values will be selected only if they occur in a row in which the column Last_Name begins with the string 'Washington'. SELECT First_Name, Last_Name FROM Employees WHERE Last_Name LIKE 'Washington%'
The keyword LIKE is used to compare strings, and it offers the feature that patterns containing wildcards can be used. For example, in the code fragment above, there is a percent sign (%) at the end of 'Washington', which signifies that any value containing the string 'Washington' plus zero or more additional characters will satisfy this selection criterion. So 'Washington' or 'Washingtonian' would be matches, but 'Washing' would not be. The other wildcard used in LIKE clauses is an underbar (_), which stands for any one character. For example, WHERE Last_Name LIKE 'Ba_man'
would match 'Batman', 'Barman', 'Badman', 'Balman', 'Bagman', 'Bamman', and so on. The code fragment below has a WHERE clause that uses the equal sign (=) to compare numbers. It selects the first and last name of the employee who is assigned car 12. SELECT First_Name, Last_Name FROM Employees WHERE Car_Number = 12
The next code fragment selects the first and last names of employees whose employee number is greater than 10005: SELECT First_Name, Last_Name FROM Employees
30
WHERE Employee_Number > 10005 WHERE clauses can get rather elaborate, with multiple conditions
and, in some DBMSs, nested conditions. This overview will not cover complicated WHERE clauses, but the following code fragment has a WHERE clause with two conditions; this query selects the first and last names of employees whose employee number is less than 10100 and who do not have a company car. SELECT First_Name, Last_Name FROM Employees WHERE Employee_Number < 10100 and Car_Number IS NULL
A special type of WHERE clause involves a join, which is explained in the next section.
Joins A distinguishing feature of relational databases is that it is possible to get data from more than one table in what is called a join. Suppose that after retrieving the names of employees who have company cars, one wanted to find out who has which car, including the make, model, and year of car. This information is stored in another table, Cars, shown in Table 1.3. Table 1.3. Cars Car Number 5 12
Make Honda Toyota
Model Civic DX Corolla
Year 1996 1999
There must be one column that appears in both tables in order to relate them to each other. This column, which must be the primary key in one table, is called the foreign key in the other table. In this case, the column that appears in two tables is Car_Number, which is the primary key for the table Cars and the foreign key in the table Employees. If the 1996 Honda Civic were
31
wrecked and deleted from the Cars table, then Car_Number 5 would also have to be removed from the Employees table in order to maintain what is called referential integrity. Otherwise, the foreign key column (Car_Number) in Employees would contain an entry that did not refer to anything in Cars. A foreign key must either be null or equal to an existing primary key value of the table to which it refers. This is different from a primary key, which may not be null. There are several null values in the Car_Number column in the table Employees because it is possible for an employee not to have a company car. The following code asks for the first and last names of employees who have company cars and for the make, model, and year of those cars. Note that the FROM clause lists both Employees and Cars because the requested data is contained in both tables. Using the table name and a dot (.) before the column name indicates which table contains the column. SELECT Employees.First_Name, Employees.Last_Name, Cars.Make, Cars.Model, Cars.Year FROM Employees, Cars WHERE Employees.Car_Number = Cars.Car_Number
This returns a result set that will look similar to the following: FIRST_NAME LAST_NAME MAKE MODEL YEAR ----------- ------------ -------- --------- ------Axel Washington Honda CivicDX 1996 Florence Wojokowski Toyota Corolla 1999
Common SQL Commands SQL commands are divided into categories, the two main ones being Data Manipulation Language (DML) commands and Data Definition Language (DDL) commands. DML commands deal with data, either retrieving it or modifying it to keep it up-to-date. DDL commands create or change tables and other database objects such as views and indexes.
32
A list of the more common DML commands follows:
SELECT — used to query and display data from a database. The SELECT statement specifies which columns to include in
the result set. The vast majority of the SQL commands used in applications are SELECT statements.
adds new rows to a table. INSERT is used to populate a newly created table or to add a new row (or rows) to an already-existing table.
INSERT —
DELETE —
removes a specified row or set of rows from a
table
changes an existing value in a column or group of columns in a table UPDATE —
The more common DDL commands follow:
creates a table with the column names the user provides. The user also needs to specify a type for the data in each column. Data types vary from one RDBMS to another, so a user might need to use metadata to establish the data types used by a particular database. CREATE TABLE is normally used less often than the data manipulation commands because a table is created only once, whereas adding or deleting rows or changing individual values generally occurs more frequently. CREATE TABLE —
33
deletes all rows and removes the table definition from the database. A JDBC API implementation is required to support the DROP TABLE command as specified by SQL92, Transitional Level. However, support for the CASCADE and RESTRICT options of DROP TABLE is optional. In addition, the behavior of DROP TABLE is implementationdefined when there are views or integrity constraints defined that reference the table being dropped.
DROP TABLE —
ALTER TABLE —
adds or removes a column from a table. It also adds or drops table constraints and alters column attributes
Result Sets and Cursors The rows that satisfy the conditions of a query are called the result set. The number of rows returned in a result set can be zero, one, or many. A user can access the data in a result set one row at a time, and a cursor provides the means to do that. A cursor can be thought of as a pointer into a file that contains the rows of the result set, and that pointer has the ability to keep track of which row is currently being accessed. A cursor allows a user to process each row of a result set from top to bottom and consequently may be used for iterative processing. Most DBMSs create a cursor automatically when a result set is generated. Earlier JDBC API versions added new capabilities for a result set's cursor, allowing it to move both forward and backward and also allowing it to move to a specified row or to a row whose position is relative to another row.
34
Transactions When one user is accessing data in a database, another user may be accessing the same data at the same time. If, for instance, the first user is updating some columns in a table at the same time the second user is selecting columns from that same table, it is possible for the second user to get partly old data and partly updated data. For this reason, DBMSs use transactions to maintain data in a consistent state (data consistency) while allowing more than one user to access a database at the same time (data concurrency). A transaction is a set of one or more SQL statements that make up a logical unit of work. A transaction ends with either a commit or a rollback, depending on whether there are any problems with data consistency or data concurrency. The commit statement makes permanent the changes resulting from the SQL statements in the transaction, and the rollback statement undoes all changes resulting from the SQL statements in the transaction. A lock is a mechanism that prohibits two transactions from manipulating the same data at the same time. For example, a table lock prevents a table from being dropped if there is an uncommitted transaction on that table. In some DBMSs, a table lock also locks all of the rows in a table. A row lock prevents two transactions from modifying the same row, or it prevents one transaction from selecting a row while another transaction is still modifying it.
Stored Procedures A stored procedure is a group of SQL statements that can be called by name. In other words, it is executable code, a miniprogram, that performs a particular task that can be invoked the same way one can call a function or method. Traditionally, stored procedures have been written in a DBMS-specific programming language. The latest generation of database products allows
35
stored procedures to be written using the Java programming language and the JDBC API. Stored procedures written in the Java programming language are bytecode portable between DBMSs. Once a stored procedure is written, it can be used and reused because a DBMS that supports stored procedures will, as its name implies, store it in the database. The following code is an example of how to create a very simple stored procedure using the Java programming language. Note that the stored procedure is just a static Java method that contains normal JDBC code. It accepts two input parameters and uses them to change an employee's car number. Do not worry if you do not understand the example at this point. The code example below is presented only to illustrate what a stored procedure looks like. You will learn how to write the code in this example in the tutorials that follow. import java.sql.*; public class UpdateCar { public static void UpdateCarNum(int carNo, int empNo) throws SQLException { Connection con = null; PreparedStatement pstmt = null; try { con = DriverManager.getConnection("jdbc:default:connection"); pstmt = con.prepareStatement( "UPDATE EMPLOYEES SET CAR_NUMBER = ? " + "WHERE EMPLOYEE_NUMBER = ?"); pstmt.setInt(1, carNo); pstmt.setInt(2, empNo); pstmt.executeUpdate();
} finally { if (pstmt != null) pstmt.close(); }
36
}
}
Metadata Databases store user data, and they also store information about the database itself. Most DBMSs have a set of system tables, which list tables in the database, column names in each table, primary keys, foreign keys, stored procedures, and so forth. Each DBMS has its own functions for getting information about table layouts and database features. JDBC provides the interface DatabaseMetaData, which a driver writer must implement so that its methods return information about the driver and/or DBMS for which the driver is written. For example, a large number of methods return whether or not the driver supports a particular functionality. This interface gives users and tools a standardized way to get metadata. In general, developers writing tools and drivers are the ones most likely to be concerned with metadata. JDBC Architecture The JDBC API supports both two-tier and three-tier processing models for database access. Figure 1: Two-tier Architecture for Data Access.
In the two-tier model, a Java application talks directly to the data source. This requires a JDBC driver that can communicate with the particular data source being accessed. A user's commands are delivered to the database or other data source, and the results of those statements are sent back to the user. The data
37
source may be located on another machine to which the user is connected via a network. This is referred to as a client/server configuration, with the user's machine as the client, and the machine housing the data source as the server. The network can be an intranet, which, for example, connects employees within a corporation, or it can be the Internet. In the three-tier model, commands are sent to a "middle tier" of services, which then sends the commands to the data source. The data source processes the commands and sends the results back to the middle tier, which then sends them to the user. MIS directors find the three-tier model very attractive because the middle tier makes it possible to maintain control over access and the kinds of updates that can be made to corporate data. Another advantage is that it simplifies the deployment of applications. Finally, in many cases, the three-tier architecture can provide performance advantages. Figure 2: Three-tier Architecture for Data Access.
Until recently, the middle tier has often been written in languages such as C or C++, which offer fast performance. However, with the introduction of optimizing compilers that translate Java bytecode into efficient machine-specific code and technologies such as Enterprise JavaBeans™, the Java platform is fast becoming the standard platform for middle-tier development. This is a big plus, making it possible to take
38
advantage of Java's robustness, multithreading, and security features. With enterprises increasingly using the Java programming language for writing server code, the JDBC API is being used more and more in the middle tier of a three-tier architecture. Some of the features that make JDBC a server technology are its support for connection pooling, distributed transactions, and disconnected rowsets. The JDBC API is also what allows access to a data source from a Java middle tier. A Relational Database Overview A database is a means of storing information in such a way that information can be retrieved from it. In simplest terms, a relational database is one that presents information in tables with rows and columns. A table is referred to as a relation in the sense that it is a collection of objects of the same type (rows). Data in a table can be related according to common keys or concepts, and the ability to retrieve related data from a table is the basis for the term relational database. A Database Management System (DBMS) handles the way data is stored, maintained, and retrieved. In the case of a relational database, a Relational Database Management System (RDBMS) performs these tasks. DBMS as used in this book is a general term that includes RDBMS.
Integrity Rules Relational tables follow certain integrity rules to ensure that the data they contain stay accurate and are always accessible. First, the rows in a relational table should all be distinct. If there are duplicate rows, there can be problems resolving which of two possible selections is the correct one. For most DBMSs, the user can specify that duplicate rows are not allowed, and if that is done, the DBMS will prevent the addition of any rows that duplicate an existing row.
39
A second integrity rule of the traditional relational model is that column values must not be repeating groups or arrays. A third aspect of data integrity involves the concept of a null value. A database takes care of situations where data may not be available by using a null value to indicate that a value is missing. It does not equate to a blank or zero. A blank is considered equal to another blank, a zero is equal to another zero, but two null values are not considered equal. When each row in a table is different, it is possible to use one or more columns to identify a particular row. This unique column or group of columns is called a primary key. Any column that is part of a primary key cannot be null; if it were, the primary key containing it would no longer be a complete identifier. This rule is referred to as entity integrity. Table 1.2 illustrates some of these relational database concepts. It has five columns and six rows, with each row representing a different employee. Table 1.2: Employees Employee_Number First_name Last_Name Date_of_Birth Car_Number 10001 Axel Washington 28-Aug-43 5 10083 Arvid Sharma 24-Nov-54 null 10120 Jonas Ginsberg 01-Jan-69 null 10005 Florence Wojokowski 04-Jul-71 12 10099 Sean Washington 21-Sep-66 null 10035 Elizabeth Yamaguchi 24-Dec-59 null The primary key for this table would generally be the employee number because each one is guaranteed to be different. (A number is also more efficient than a string for making comparisons.) It would also be possible to use First_Name and Last_Name because the combination of the two also identifies just one row in our sample database. Using the last name alone
40
would not work because there are two employees with the last name of "Washington." In this particular case the first names are all different, so one could conceivably use that column as a primary key, but it is best to avoid using a column where duplicates could occur. If Elizabeth Taylor gets a job at this company and the primary key is First_Name, the RDBMS will not allow her name to be added (if it has been specified that no duplicates are permitted). Because there is already an Elizabeth in the table, adding a second one would make the primary key useless as a way of identifying just one row. Note that although using First_Name and Last_Name is a unique composite key for this example, it might not be unique in a larger database. Note also that Table 1.2 assumes that there can be only one car per employee. SELECT
Statements
SQL is a language designed to be used with relational databases. There is a set of basic SQL commands that is considered standard and is used by all RDBMSs. For example, all RDBMSs use the SELECT statement. A SELECT statement, also called a query, is used to get information from a table. It specifies one or more column headings, one or more tables from which to select, and some criteria for selection. The RDBMS returns rows of the column entries that satisfy the stated requirements. A SELECT statement such as the following will fetch the first and last names of employees who have company cars: SELECT First_Name, Last_Name FROM Employees WHERE Car_Number IS NOT NULL
The result set (the set of rows that satisfy the requirement of not having null in the Car_Number column) follows. The first name and last name are printed for each row that satisfies the requirement because the SELECT statement (the first line)
41
specifies the columns First_Name and Last_Name. The FROM clause (the second line) gives the table from which the columns will be selected. FIRST_NAME ---------Axel Florence
LAST_NAME ----------Washington Wojokowski
The following code produces a result set that includes the whole table because it asks for all of the columns in the table Employees with no restrictions (no WHERE clause). Note that SELECT * means "SELECT all columns." SELECT * FROM Employees WHERE
Clauses
The WHERE clause in a SELECT statement provides the criteria for selecting values. For example, in the following code fragment, values will be selected only if they occur in a row in which the column Last_Name begins with the string 'Washington'. SELECT First_Name, Last_Name FROM Employees WHERE Last_Name LIKE 'Washington%'
The keyword LIKE is used to compare strings, and it offers the feature that patterns containing wildcards can be used. For example, in the code fragment above, there is a percent sign (%) at the end of 'Washington', which signifies that any value containing the string 'Washington' plus zero or more additional characters will satisfy this selection criterion. So 'Washington' or 'Washingtonian' would be matches, but 'Washing' would not be. The other wildcard used in LIKE clauses is an underbar (_), which stands for any one character. For example, WHERE Last_Name LIKE 'Ba_man'
42
would match 'Batman', 'Barman', 'Badman', 'Balman', 'Bagman', 'Bamman', and so on. The code fragment below has a WHERE clause that uses the equal sign (=) to compare numbers. It selects the first and last name of the employee who is assigned car 12. SELECT First_Name, Last_Name FROM Employees WHERE Car_Number = 12
The next code fragment selects the first and last names of employees whose employee number is greater than 10005: SELECT First_Name, Last_Name FROM Employees WHERE Employee_Number > 10005 WHERE clauses can get rather elaborate, with multiple conditions
and, in some DBMSs, nested conditions. This overview will not cover complicated WHERE clauses, but the following code fragment has a WHERE clause with two conditions; this query selects the first and last names of employees whose employee number is less than 10100 and who do not have a company car. SELECT First_Name, Last_Name FROM Employees WHERE Employee_Number < 10100 and Car_Number IS NULL
A special type of WHERE clause involves a join, which is explained in the next section.
Joins A distinguishing feature of relational databases is that it is possible to get data from more than one table in what is called a join. Suppose that after retrieving the names of employees who have company cars, one wanted to find out who has which car,
43
including the make, model, and year of car. This information is stored in another table, Cars, shown in Table 1.3. Table 1.3. Cars Car Number 5 12
Make Honda Toyota
Model Civic DX Corolla
Year 1996 1999
There must be one column that appears in both tables in order to relate them to each other. This column, which must be the primary key in one table, is called the foreign key in the other table. In this case, the column that appears in two tables is Car_Number, which is the primary key for the table Cars and the foreign key in the table Employees. If the 1996 Honda Civic were wrecked and deleted from the Cars table, then Car_Number 5 would also have to be removed from the Employees table in order to maintain what is called referential integrity. Otherwise, the foreign key column (Car_Number) in Employees would contain an entry that did not refer to anything in Cars. A foreign key must either be null or equal to an existing primary key value of the table to which it refers. This is different from a primary key, which may not be null. There are several null values in the Car_Number column in the table Employees because it is possible for an employee not to have a company car. The following code asks for the first and last names of employees who have company cars and for the make, model, and year of those cars. Note that the FROM clause lists both Employees and Cars because the requested data is contained in both tables. Using the table name and a dot (.) before the column name indicates which table contains the column. SELECT Employees.First_Name, Employees.Last_Name, Cars.Make, Cars.Model, Cars.Year FROM Employees, Cars WHERE Employees.Car_Number = Cars.Car_Number
44
This returns a result set that will look similar to the following: FIRST_NAME LAST_NAME MAKE MODEL YEAR ----------- ------------ -------- --------- ------Axel Washington Honda CivicDX 1996 Florence Wojokowski Toyota Corolla 1999
Common SQL Commands SQL commands are divided into categories, the two main ones being Data Manipulation Language (DML) commands and Data Definition Language (DDL) commands. DML commands deal with data, either retrieving it or modifying it to keep it up-to-date. DDL commands create or change tables and other database objects such as views and indexes. A list of the more common DML commands follows:
SELECT — used to query and display data from a database. The SELECT statement specifies which columns to include in
the result set. The vast majority of the SQL commands used in applications are SELECT statements.
adds new rows to a table. INSERT is used to populate a newly created table or to add a new row (or rows) to an already-existing table.
INSERT —
DELETE —
removes a specified row or set of rows from a
table
changes an existing value in a column or group of columns in a table UPDATE —
45
The more common DDL commands follow: creates a table with the column names the user provides. The user also needs to specify a type for the data in each column. Data types vary from one RDBMS to another, so a user might need to use metadata to establish the data types used by a particular database. CREATE TABLE is normally used less often than the data manipulation commands because a table is created only once, whereas adding or deleting rows or changing individual values generally occurs more frequently.
CREATE TABLE —
DROP TABLE —
ALTER TABLE —
deletes all rows and removes the table definition from the database. A JDBC API implementation is required to support the DROP TABLE command as specified by SQL92, Transitional Level. However, support for the CASCADE and RESTRICT options of DROP TABLE is optional. In addition, the behavior of DROP TABLE is implementationdefined when there are views or integrity constraints defined that reference the table being dropped.
adds or removes a column from a table. It also adds or drops table constraints and alters column attributes
Result Sets and Cursors The rows that satisfy the conditions of a query are called the result set. The number of rows returned in a result set can be
46
zero, one, or many. A user can access the data in a result set one row at a time, and a cursor provides the means to do that. A cursor can be thought of as a pointer into a file that contains the rows of the result set, and that pointer has the ability to keep track of which row is currently being accessed. A cursor allows a user to process each row of a result set from top to bottom and consequently may be used for iterative processing. Most DBMSs create a cursor automatically when a result set is generated. Earlier JDBC API versions added new capabilities for a result set's cursor, allowing it to move both forward and backward and also allowing it to move to a specified row or to a row whose position is relative to another row.
Transactions When one user is accessing data in a database, another user may be accessing the same data at the same time. If, for instance, the first user is updating some columns in a table at the same time the second user is selecting columns from that same table, it is possible for the second user to get partly old data and partly updated data. For this reason, DBMSs use transactions to maintain data in a consistent state (data consistency) while allowing more than one user to access a database at the same time (data concurrency). A transaction is a set of one or more SQL statements that make up a logical unit of work. A transaction ends with either a commit or a rollback, depending on whether there are any problems with data consistency or data concurrency. The commit statement makes permanent the changes resulting from the SQL statements in the transaction, and the rollback statement undoes all changes resulting from the SQL statements in the transaction. A lock is a mechanism that prohibits two transactions from manipulating the same data at the same time. For example, a table lock prevents a table from being dropped if there is an
47
uncommitted transaction on that table. In some DBMSs, a table lock also locks all of the rows in a table. A row lock prevents two transactions from modifying the same row, or it prevents one transaction from selecting a row while another transaction is still modifying it.
Stored Procedures A stored procedure is a group of SQL statements that can be called by name. In other words, it is executable code, a miniprogram, that performs a particular task that can be invoked the same way one can call a function or method. Traditionally, stored procedures have been written in a DBMS-specific programming language. The latest generation of database products allows stored procedures to be written using the Java programming language and the JDBC API. Stored procedures written in the Java programming language are bytecode portable between DBMSs. Once a stored procedure is written, it can be used and reused because a DBMS that supports stored procedures will, as its name implies, store it in the database. The following code is an example of how to create a very simple stored procedure using the Java programming language. Note that the stored procedure is just a static Java method that contains normal JDBC code. It accepts two input parameters and uses them to change an employee's car number. Do not worry if you do not understand the example at this point. The code example below is presented only to illustrate what a stored procedure looks like. You will learn how to write the code in this example in the tutorials that follow. import java.sql.*; public class UpdateCar { public static void UpdateCarNum(int carNo, int empNo) throws SQLException {
Metadata Databases store user data, and they also store information about the database itself. Most DBMSs have a set of system tables, which list tables in the database, column names in each table, primary keys, foreign keys, stored procedures, and so forth. Each DBMS has its own functions for getting information about table layouts and database features. JDBC provides the interface DatabaseMetaData, which a driver writer must implement so that its methods return information about the driver and/or DBMS for which the driver is written. For example, a large number of methods return whether or not the driver supports a particular functionality. This interface gives users and tools a standardized way to get metadata. In general, developers writing tools and drivers are the ones most likely to be concerned with metadata.
Deployment Packaging Programs in JAR Files The JavaTM Archive (JAR) file format enables you to bundle multiple files into a single archive file. Typically a JAR file
49
contains the class files and auxiliary resources associated with applets and applications. The JAR file format provides many benefits:
Security: You can digitally sign the contents of a JAR file. Users who recognize your signature can then optionally grant your software security privileges it wouldn't otherwise have. Decreased download time: If your applet is bundled in a JAR file, the applet's class files and associated resources can be downloaded to a browser in a single HTTP transaction without the need for opening a new connection for each file. Compression: The JAR format allows you to compress your files for efficient storage. Packaging for extensions: The extensions framework provides a means by which you can add functionality to the Java core platform, and the JAR file format defines the packaging for extensions. Java 3D™ and JavaMail™ are examples of extensions developed by SunTM. By using the JAR file format, you can turn your software into extensions as well. Package Sealing: Packages stored in JAR files can be optionally sealed so that the package can enforce version consistency. Sealing a package within a JAR file means that all classes defined in that package must be found in the same JAR file. Package Versioning: A JAR file can hold data about the files it contains, such as vendor and version information. Portability: The mechanism for handling JAR files is a standard part of the Java platform's core API.
50
Threads Reflection
The Reflection API
Uses of Reflection Reflection is commonly used by programs which require the ability to examine or modify the runtime behavior of applications running in the Java virtual machine. This is a relatively advanced feature and should be used only by developers who have a strong grasp of the fundamentals of the language. With that caveat in mind, reflection is a powerful technique and can enable applications to perform operations which would otherwise be impossible. Extensibility Features An application may make use of external, user-defined classes by creating instances of extensibility objects using their fully-qualified names. Class Browsers and Visual Development Environments A class browser needs to be able to enumerate the members of classes. Visual development environments can benefit from making use of type information available in reflection to aid the developer in writing correct code. Debuggers and Test Tools Debuggers need to be able to examine private members on classes. Test harnesses can make use of reflection to systematically call a discoverable set APIs defined on a class, to insure a high level of code coverage in a test suite.
Drawbacks of Reflection Reflection is powerful, but should not be used indiscriminately. If it is possible to perform an operation without using reflection, then it is preferable to avoid using it. The following concerns should be kept in mind when accessing code via reflection. Performance Overhead
51
Because reflection involves types that are dynamically resolved, certain Java virtual machine optimizations can not be performed. Consequently, reflective operations have slower performance than their non-reflective counterparts, and should be avoided in sections of code which are called frequently in performance-sensitive applications. Security Restrictions Reflection requires a runtime permission which may not be present when running under a security manager. This is in an important consideration for code which has to run in a restricted security context, such as in an Applet. Exposure of Internals Since reflection allows code to perform operations that would be illegal in non-reflective code, such as accessing private fields and methods, the use of reflection can result in unexpected side-effects, which may render code dysfunctional and may destroy portability. Reflective code breaks abstractions and therefore may change behavior with upgrades of the platform.
Trail Lessons This trail covers common uses of reflection for accessing and manipulating classes, fields, methods, and constructors. Each lesson contains code examples, tips, and troubleshooting information. Classes This lesson shows the various ways to obtain a Class object and use it to examine properties of a class, including its declaration and contents. Members This lesson describes how to use the Reflection APIs to find the fields, methods, and constructors of a class. Examples are provided for setting and getting field values, invoking methods, and creating new instances of objects using specific constructors.
52
Arrays and Enumerated Types This lesson introduces two special types of classes: arrays, which are generated at runtime, and enum types, which define unique named object instances. Sample code shows how to retrieve the component type for an array and how to set and get fields with array or enum types. Note: The examples in this trail are designed for experimenting with the Reflection APIs. The handling of exceptions therefore is not the same as would be used in production code. In particular, in production code it is not recommended to dump stack traces that are visible to the user. Classes Every object is either a reference or primitive type. Reference types all inherit from java.lang.Object. Classes, enums, arrays, and interfaces are all reference types. There is a fixed set of primitive types: boolean, byte, short, int, long, char, float, and double. Examples of reference types include java.lang.String, all of the wrapper classes for primitive types such as java.lang.Double, the interface java.io.Serializable, and the enum javax.swing.SortOrder. For every type of object, the Java virtual machine instantiates an immutable instance of java.lang.Class which provides methods to examine the runtime properties of the object including its members and type information. Class also provides the ability to create new classes and objects. Most importantly, it is the entry point for all of the Reflection APIs. This lesson covers the most commonly used reflection operations involving classes:
Retrieving Class Objects describes the ways to get a Class Examining Class Modifiers and Types shows how to access the class declaration information Discovering Class Members illustrates how to list the constructors, fields, methods, and nested classes in a class
53
Troubleshooting describes common errors encountered when using Class
Examining Class Modifiers and Types A class may be declared with one or more modifiers which affect its runtime behavior:
Access modifiers: public, protected, and private Modifier requiring override: abstract Modifier restricting to one instance: static Modifier prohibiting value modification: final Modifier forcing strict floating point behavior: strictfp Annotations
Not all modifiers are allowed on all classes, for example an interface cannot be final and an enum cannot be abstract. java.lang.reflect.Modifier contains declarations for all possible modifiers. It also contains methods which may be used to decode the set of modifiers returned by Class.getModifiers(). The ClassDeclarationSpy example shows how to obtain the declaration components of a class including the modifiers, generic type parameters, implemented interfaces, and the inheritance path. Since Class implements the java.lang.reflect.AnnotatedElement interface it is also possible to query the runtime annotations. import import import import import import import import
public class ClassDeclarationSpy { public static void main(String... args) { try { Class> c = Class.forName(args[0]); out.format("Class:%n %s%n%n", c.getCanonicalName());
54
out.format("Modifiers:%n %s%n%n", Modifier.toString(c.getModifiers())); out.format("Type Parameters:%n"); TypeVariable[] tv = c.getTypeParameters(); if (tv.length != 0) { out.format(" "); for (TypeVariable t : tv) out.format("%s ", t.getName()); out.format("%n%n"); } else { out.format(" -- No Type Parameters --%n%n"); } out.format("Implemented Interfaces:%n"); Type[] intfs = c.getGenericInterfaces(); if (intfs.length != 0) { for (Type intf : intfs) out.format(" %s%n", intf.toString()); out.format("%n"); } else { out.format(" -- No Implemented Interfaces --%n %n");
} out.format("Inheritance Path:%n"); List l = new ArrayList(); printAncestor(c, l); if (l.size() != 0) { for (Class> cl : l) out.format(" %s%n", cl.getCanonicalName()); out.format("%n"); } else { out.format(" -- No Super Classes --%n%n"); } out.format("Annotations:%n"); Annotation[] ann = c.getAnnotations(); if (ann.length != 0) { for (Annotation a : ann) out.format(" %s%n", a.toString()); out.format("%n"); } else {
55
}
out.format("
-- No Annotations --%n%n");
// production code should handle this exception more gracefully } catch (ClassNotFoundException x) { x.printStackTrace(); } } private static void printAncestor(Class> c, List
A few samples of the output follows. User input is in italics. $ java ClassDeclarationSpy java.util.concurrent.ConcurrentNavigableMap Class: java.util.concurrent.ConcurrentNavigableMap Modifiers: public abstract interface Type Parameters: K V Implemented Interfaces: java.util.concurrent.ConcurrentMap java.util.NavigableMap Inheritance Path: -- No Super Classes -Annotations: -- No Annotations --
This is the actual declaration for java.util.concurrent.ConcurrentNavigableMap in the source code: public interface ConcurrentNavigableMap
56
extends ConcurrentMap, NavigableMap Note that since this is an interface, it is implicitly abstract. The compiler adds
this modifier for every interface. Also, this declaration contains two generic type parameters, K and V. The example code simply prints the names of these parameters, but is it possible to retrieve additional information about them using methods in java.lang.reflect.TypeVariable. Interfaces may also implement other interfaces as shown above. $ java ClassDeclarationSpy "[Ljava.lang.String;" Class: java.lang.String[] Modifiers: public abstract final Type Parameters: -- No Type Parameters -Implemented Interfaces: interface java.lang.Cloneable interface java.io.Serializable Inheritance Path: java.lang.Object Annotations: -- No Annotations --
Since arrays are runtime objects, all of the type information is defined by the Java virtual machine. In particular, arrays implement Cloneable and java.io.Serializable and their direct superclass is always Object. $ java ClassDeclarationSpy java.io.InterruptedIOException Class: java.io.InterruptedIOException Modifiers: public Type Parameters: -- No Type Parameters -Implemented Interfaces: -- No Implemented Interfaces --
From the inheritance path, it may be deduced that java.io.InterruptedIOException is a checked exception because RuntimeException is not present. $ java ClassDeclarationSpy java.security.Identity Class: java.security.Identity Modifiers: public abstract Type Parameters: -- No Type Parameters -Implemented Interfaces: interface java.security.Principal interface java.io.Serializable Inheritance Path: java.lang.Object Annotations: @java.lang.Deprecated() This output shows that java.security.Identity, a deprecated API, possesses the annotation java.lang.Deprecated. This may be used by reflective code to
detect deprecated APIs. Note: Not all annotations are available via reflection. Only those which have a java.lang.annotation.RetentionPolicy of RUNTIME are accessible. Of the three annotations pre-defined in the language @Deprecated, @Override, and @SuppressWarnings only @Deprecated is available at runtime.
58
Security
Security Features in Java SE In this trail you'll learn how the built-in Java™ security features protect you from malevolent programs. You'll see how to use tools to control access to resources, to generate and to check digital signatures, and to create and to manage keys needed for signature generation and checking. You'll also see how to incorporate cryptography services, such as digital signature generation and checking, into your programs. The security features provided by the Java Development Kit (JDK™) are intended for a variety of audiences:
Users running programs: Built-in security functionality protects you from malevolent programs (including viruses), maintains the privacy of your files and information about you, and authenticates the identity of each code provider. You can subject applications and applets to security controls when you need to.
Developers: You can use API methods to incorporate security functionality into your programs, including cryptography services and security checks. The API framework enables you to define and integrate your own permissions (controlling access to specific resources), cryptography service implementations, security manager implementations, and policy implementations. In addition, classes are provided for management of your public/private key pairs and public key certificates from people you trust.
Systems administrators, developers, and users: JDK tools manage your keystore (database of keys and certificates); generate digital signatures for JAR files, and verify the authenticity of such signatures and the integrity of the signed contents; and create and modify the policy files that define your installation's security policy.
59
RMI
The Java Remote Method Invocation (RMI) system allows an object running in one Java virtual machine to invoke methods on an object running in another Java virtual machine. RMI provides for remote communication between programs written in the Java programming language. Note: If you are connecting to an existing IDL program, you should use Java IDL rather than RMI. This trail provides a brief overview of the RMI system and then walks through a complete client/server example that uses RMI's unique capabilities to load and to execute user-defined tasks at runtime. The server in the example implements a generic compute engine, which the client uses to compute the value of . An Overview of RMI Applications RMI applications often comprise two separate programs, a server and a client. A typical server program creates some remote objects, makes references to these objects accessible, and waits for clients to invoke methods on these objects. A typical client program obtains a remote reference to one or more remote objects on a server and then invokes methods on them. RMI provides the mechanism by which the server and the client communicate and pass information back and forth. Such an application is sometimes referred to as a distributed object application. Distributed object applications need to do the following:
Locate remote objects. Applications can use various mechanisms to obtain references to remote objects. For example, an application can register its remote objects with RMI's simple naming facility, the RMI registry. Alternatively, an application can pass and return remote object references as part of other remote invocations. Communicate with remote objects. Details of communication between remote objects are handled by RMI.
60
To the programmer, remote communication looks similar to regular Java method invocations. Load class definitions for objects that are passed around. Because RMI enables objects to be passed back and forth, it provides mechanisms for loading an object's class definitions as well as for transmitting an object's data.
The following illustration depicts an RMI distributed application that uses the RMI registry to obtain a reference to a remote object. The server calls the registry to associate (or bind) a name with a remote object. The client looks up the remote object by its name in the server's registry and then invokes a method on it. The illustration also shows that the RMI system uses an existing web server to load class definitions, from server to client and from client to server, for objects when needed.
Advantages of Dynamic Code Loading One of the central and unique features of RMI is its ability to download the definition of an object's class if the class is not defined in the receiver's Java virtual machine. All of the types and behavior of an object, previously available only in a single Java virtual machine, can be transmitted to another, possibly remote, Java virtual machine. RMI passes objects by their actual classes, so the behavior of the objects is not changed when they are sent to another Java virtual machine. This capability enables new types and behaviors to be introduced into a remote Java virtual machine, thus dynamically extending the behavior of an
61
application. The compute engine example in this trail uses this capability to introduce new behavior to a distributed program.
Remote Interfaces, Objects, and Methods Like any other Java application, a distributed application built by using Java RMI is made up of interfaces and classes. The interfaces declare methods. The classes implement the methods declared in the interfaces and, perhaps, declare additional methods as well. In a distributed application, some implementations might reside in some Java virtual machines but not others. Objects with methods that can be invoked across Java virtual machines are called remote objects. An object becomes remote by implementing a remote interface, which has the following characteristics:
A remote interface extends the interface java.rmi.Remote. Each method of the interface declares java.rmi.RemoteException in its throws clause, in addition to any application-specific exceptions.
RMI treats a remote object differently from a non-remote object when the object is passed from one Java virtual machine to another Java virtual machine. Rather than making a copy of the implementation object in the receiving Java virtual machine, RMI passes a remote stub for a remote object. The stub acts as the local representative, or proxy, for the remote object and basically is, to the client, the remote reference. The client invokes a method on the local stub, which is responsible for carrying out the method invocation on the remote object. A stub for a remote object implements the same set of remote interfaces that the remote object implements. This property enables a stub to be cast to any of the interfaces that the remote object implements. However, only those methods defined in a remote interface are available to be called from the receiving Java virtual machine.
62
Creating Distributed Applications by Using RMI Using RMI to develop a distributed application involves these general steps: 1. Designing and implementing the components of your distributed application. 2. Compiling sources. 3. Making classes network accessible. 4. Starting the application.
Designing and Implementing the Application Components First, determine your application architecture, including which components are local objects and which components are remotely accessible. This step includes:
Defining the remote interfaces. A remote interface specifies the methods that can be invoked remotely by a client. Clients program to remote interfaces, not to the implementation classes of those interfaces. The design of such interfaces includes the determination of the types of objects that will be used as the parameters and return values for these methods. If any of these interfaces or classes do not yet exist, you need to define them as well. Implementing the remote objects. Remote objects must implement one or more remote interfaces. The remote object class may include implementations of other interfaces and methods that are available only locally. If any local classes are to be used for parameters or return values of any of these methods, they must be implemented as well. Implementing the clients. Clients that use remote objects can be implemented at any time after the remote interfaces are defined, including after the remote objects have been deployed.
63
Compiling Sources As with any Java program, you use the javac compiler to compile the source files. The source files contain the declarations of the remote interfaces, their implementations, any other server classes, and the client classes. Note: With versions prior to Java Platform, Standard Edition 5.0, an additional step was required to build stub classes, by using the rmic compiler. However, this step is no longer necessary.
Making Classes Network Accessible In this step, you make certain class definitions network accessible, such as the definitions for the remote interfaces and their associated types, and the definitions for classes that need to be downloaded to the clients or servers. Classes definitions are typically made network accessible through a web server.
Starting the Application Starting the application includes running the RMI remote object registry, the server, and the client. The rest of this section walks through the steps used to create a compute engine.
Building a Generic Compute Engine This trail focuses on a simple, yet powerful, distributed application called a compute engine. The compute engine is a remote object on the server that takes tasks from clients, runs the tasks, and returns any results. The tasks are run on the machine where the server is running. This type of distributed application can enable a number of client machines to make use of a particularly powerful machine or a machine that has specialized hardware. The novel aspect of the compute engine is that the tasks it runs do not need to be defined when the compute engine is written or
64
started. New kinds of tasks can be created at any time and then given to the compute engine to be run. The only requirement of a task is that its class implement a particular interface. The code needed to accomplish the task can be downloaded by the RMI system to the compute engine. Then, the compute engine runs the task, using the resources on the machine on which the compute engine is running. The ability to perform arbitrary tasks is enabled by the dynamic nature of the Java platform, which is extended to the network by RMI. RMI dynamically loads the task code into the compute engine's Java virtual machine and runs the task without prior knowledge of the class that implements the task. Such an application, which has the ability to download code dynamically, is often called a behavior-based application. Such applications usually require full agent-enabled infrastructures. With RMI, such applications are part of the basic mechanisms for distributed computing on the Java platform. Writing an RMI Server The compute engine server accepts tasks from clients, runs the tasks, and returns any results. The server code consists of an interface and a class. The interface defines the methods that can be invoked from the client. Essentially, the interface defines the client's view of the remote object. The class provides the implementation. Designing a Remote Interface This section explains the Compute interface, which provides the connection between the client and the server. You will also learn about the RMI API, which supports this communication. Implementing a Remote Interface This section explores the class that implements the Compute interface, thereby implementing a remote object. This class also
65
provides the rest of the code that makes up the server program, including a main method that creates an instance of the remote object, registers it with the RMI registry, and sets up a security manager. Designing a Remote Interface At the core of the compute engine is a protocol that enables tasks to be submitted to the compute engine, the compute engine to run those tasks, and the results of those tasks to be returned to the client. This protocol is expressed in the interfaces that are supported by the compute engine. The remote communication for this protocol is illustrated in the following figure.
Each interface contains a single method. The compute engine's remote interface, Compute, enables tasks to be submitted to the engine. The client interface, Task, defines how the compute engine executes a submitted task. The compute.Compute interface defines the remotely accessible part, the compute engine itself. Here is the source code for the Compute interface: package compute; import java.rmi.Remote; import java.rmi.RemoteException; public interface Compute extends Remote { T executeTask(Task t) throws RemoteException; }
By extending the interface java.rmi.Remote, the Compute interface identifies itself as an interface whose methods can be invoked from another Java virtual machine. Any object that implements this interface can be a remote object. As a member of a remote interface, the executeTask method is a remote method. Therefore, this method must be defined as being capable of throwing a java.rmi.RemoteException. This exception is thrown by the RMI system from a remote method invocation to indicate that either a communication failure or a
66
protocol error has occurred. A RemoteException is a checked exception, so any code invoking a remote method needs to handle this exception by either catching it or declaring it in its throws clause. The second interface needed for the compute engine is the Task interface, which is the type of the parameter to the executeTask method in the Compute interface. The compute.Task interface defines the interface between the compute engine and the work that it needs to do, providing the way to start the work. Here is the source code for the Task interface: package compute; public interface Task { T execute(); }
The Task interface defines a single method, execute, which has no parameters and throws no exceptions. Because the interface does not extend Remote, the method in this interface doesn't need to list java.rmi.RemoteException in its throws clause. The Task interface has a type parameter, T, which represents the result type of the task's computation. This interface's execute method returns the result of the computation and thus its return type is T. The Compute interface's executeTask method, in turn, returns the result of the execution of the Task instance passed to it. Thus, the executeTask method has its own type parameter, T, that associates its own return type with the result type of the passed Task instance. RMI uses the Java object serialization mechanism to transport objects by value between Java virtual machines. For an object to be considered serializable, its class must implement the java.io.Serializable marker interface. Therefore, classes that implement the Task interface must also implement Serializable, as must the classes of objects used for task results. Different kinds of tasks can be run by a Compute object as long as they are implementations of the Task type. The classes that implement this interface can
67
contain any data needed for the computation of the task and any other methods needed for the computation. Here is how RMI makes this simple compute engine possible. Because RMI can assume that the Task objects are written in the Java programming language, implementations of the Task object that were previously unknown to the compute engine are downloaded by RMI into the compute engine's Java virtual machine as needed. This capability enables clients of the compute engine to define new kinds of tasks to be run on the server machine without needing the code to be explicitly installed on that machine. The compute engine, implemented by the ComputeEngine class, implements the Compute interface, enabling different tasks to be submitted to it by calls to its executeTask method. These tasks are run using the task's implementation of the execute method and the results, are returned to the remote client. Implementing a Remote Interface This section discusses the task of implementing a class for the compute engine. In general, a class that implements a remote interface should at least do the following:
Declare the remote interfaces being implemented Define the constructor for each remote object Provide an implementation for each remote method in the remote interfaces
An RMI server program needs to create the initial remote objects and export them to the RMI runtime, which makes them available to receive incoming remote invocations. This setup procedure can be either encapsulated in a method of the remote object implementation class itself or included in another class entirely. The setup procedure should do the following:
Create and install a security manager Create and export one or more remote objects Register at least one remote object with the RMI registry (or with another naming service, such as a service accessible
68
through the Java Naming and Directory Interface) for bootstrapping purposes The complete implementation of the compute engine follows. The engine.ComputeEngine class implements the remote interface Compute and also includes the main method for setting up the compute engine. Here is the source code for the ComputeEngine class: package engine; import import import import import import
The following sections discuss each component of the compute engine implementation.
Declaring the Remote Interfaces Being Implemented The implementation class for the compute engine is declared as follows: public class ComputeEngine implements Compute
This declaration states that the class implements the Compute remote interface and therefore can be used for a remote object. The ComputeEngine class defines a remote object implementation class that implements a single remote interface and no other interfaces. The ComputeEngine class also contains two executable program elements that can only be invoked locally. The first of these elements is a constructor for ComputeEngine instances. The second of these elements is a main method that is used to create a ComputeEngine instance and make it available to clients.
Defining the Constructor for the Remote Object The ComputeEngine class has a single constructor that takes no arguments. The code for the constructor is as follows: public ComputeEngine() { super(); }
This constructor just invokes the superclass constructor, which is the no-argument constructor of the Object class. Although the superclass constructor gets invoked even if omitted from the ComputeEngine constructor, it is included for clarity.
70
Providing Implementations for Each Remote Method The class for a remote object provides implementations for each remote method specified in the remote interfaces. The Compute interface contains a single remote method, executeTask, which is implemented as follows: public T executeTask(Task t) { return t.execute(); }
This method implements the protocol between the ComputeEngine remote object and its clients. Each client provides the ComputeEngine with a Task object that has a particular implementation of the Task interface's execute method. The ComputeEngine executes each client's task and returns the result of the task's execute method directly to the client.
Passing Objects in RMI Arguments to or return values from remote methods can be of almost any type, including local objects, remote objects, and primitive data types. More precisely, any entity of any type can be passed to or from a remote method as long as the entity is an instance of a type that is a primitive data type, a remote object, or a serializable object, which means that it implements the interface java.io.Serializable. Some object types do not meet any of these criteria and thus cannot be passed to or returned from a remote method. Most of these objects, such as threads or file descriptors, encapsulate information that makes sense only within a single address space. Many of the core classes, including the classes in the packages java.lang and java.util, implement the Serializable interface. The rules governing how arguments and return values are passed are as follows:
Remote objects are essentially passed by reference. A remote object reference is a stub, which is a client-side
71
proxy that implements the complete set of remote interfaces that the remote object implements. Local objects are passed by copy, using object serialization. By default, all fields are copied except fields that are marked static or transient. Default serialization behavior can be overridden on a class-by-class basis.
Passing a remote object by reference means that any changes made to the state of the object by remote method invocations are reflected in the original remote object. When a remote object is passed, only those interfaces that are remote interfaces are available to the receiver. Any methods defined in the implementation class or defined in non-remote interfaces implemented by the class are not available to that receiver. For example, if you were to pass a reference to an instance of the ComputeEngine class, the receiver would have access only to the compute engine's executeTask method. That receiver would not see the ComputeEngine constructor, its main method, or its implementation of any methods of java.lang.Object. In the parameters and return values of remote method invocations, objects that are not remote objects are passed by value. Thus, a copy of the object is created in the receiving Java virtual machine. Any changes to the object's state by the receiver are reflected only in the receiver's copy, not in the sender's original instance. Any changes to the object's state by the sender are reflected only in the sender's original instance, not in the receiver's copy.
Implementing the Server's main Method The most complex method of the ComputeEngine implementation is the main method. The main method is used to start the ComputeEngine and therefore needs to do the necessary initialization and housekeeping to prepare the server to accept calls from clients. This method is not a remote method, which
72
means that it cannot be invoked from a different Java virtual machine. Because the main method is declared static, the method is not associated with an object at all but rather with the class ComputeEngine.
Creating and Installing a Security Manager The main method's first task is to create and install a security manager, which protects access to system resources from untrusted downloaded code running within the Java virtual machine. A security manager determines whether downloaded code has access to the local file system or can perform any other privileged operations. If an RMI program does not install a security manager, RMI will not download classes (other than from the local class path) for objects received as arguments or return values of remote method invocations. This restriction ensures that the operations performed by downloaded code are subject to a security policy. Here's the code that creates and installs a security manager: if (System.getSecurityManager() == null) { System.setSecurityManager(new SecurityManager()); }
Making the Remote Object Available to Clients Next, the main method creates an instance of ComputeEngine and exports it to the RMI runtime with the following statements: Compute engine = new ComputeEngine(); Compute stub = (Compute) UnicastRemoteObject.exportObject(engine, 0);
The static UnicastRemoteObject.exportObject method exports the supplied remote object so that it can receive invocations of its remote methods from remote clients. The second argument, an int, specifies which TCP port to use to listen for incoming remote invocation requests for the object. It is common to use the value zero, which specifies the use of an anonymous port. The actual
73
port will then be chosen at runtime by RMI or the underlying operating system. However, a non-zero value can also be used to specify a specific port to use for listening. Once the exportObject invocation has returned successfully, the ComputeEngine remote object is ready to process incoming remote invocations. The exportObject method returns a stub for the exported remote object. Note that the type of the variable stub must be Compute, not ComputeEngine, because the stub for a remote object only implements the remote interfaces that the exported remote object implements. The exportObject method declares that it can throw a RemoteException, which is a checked exception type. The main method handles this exception with its try/catch block. If the exception were not handled in this way, RemoteException would have to be declared in the throws clause of the main method. An attempt to export a remote object can throw a RemoteException if the necessary communication resources are not available, such as if the requested port is bound for some other purpose. Before a client can invoke a method on a remote object, it must first obtain a reference to the remote object. Obtaining a reference can be done in the same way that any other object reference is obtained in a program, such as by getting the reference as part of the return value of a method or as part of a data structure that contains such a reference. The system provides a particular type of remote object, the RMI registry, for finding references to other remote objects. The RMI registry is a simple remote object naming service that enables clients to obtain a reference to a remote object by name. The registry is typically only used to locate the first remote object that an RMI client needs to use. That first remote object might then provide support for finding other objects.
74
The java.rmi.registry.Registry remote interface is the API for binding (or registering) and looking up remote objects in the registry. The java.rmi.registry.LocateRegistry class provides static methods for synthesizing a remote reference to a registry at a particular network address (host and port). These methods create the remote reference object containing the specified network address without performing any remote communication. LocateRegistry also provides static methods for creating a new registry in the current Java virtual machine, although this example does not use those methods. Once a remote object is registered with an RMI registry on the local host, clients on any host can look up the remote object by name, obtain its reference, and then invoke remote methods on the object. The registry can be shared by all servers running on a host, or an individual server process can create and use its own registry. The ComputeEngine class creates a name for the object with the following statement: String name = "Compute";
The code then adds the name to the RMI registry running on the server. This step is done later with the following statements: Registry registry = LocateRegistry.getRegistry(); registry.rebind(name, stub);
This rebind invocation makes a remote call to the RMI registry on the local host. Like any remote call, this call can result in a RemoteException being thrown, which is handled by the catch block at the end of the main method. Note the following about the Registry.rebind invocation:
The no-argument overload of LocateRegistry.getRegistry synthesizes a reference to a registry on the local host and on the default registry port, 1099. You must use an overload
75
that has an int parameter if the registry is created on a port other than 1099. When a remote invocation on the registry is made, a stub for the remote object is passed instead of a copy of the remote object itself. Remote implementation objects, such as instances of ComputeEngine, never leave the Java virtual machine in which they were created. Thus, when a client performs a lookup in a server's remote object registry, a copy of the stub is returned. Remote objects in such cases are thus effectively passed by (remote) reference rather than by value. For security reasons, an application can only bind, unbind, or rebind remote object references with a registry running on the same host. This restriction prevents a remote client from removing or overwriting any of the entries in a server's registry. A lookup, however, can be requested from any host, local or remote.
Once the server has registered with the local RMI registry, it prints a message indicating that it is ready to start handling calls. Then, the main method completes. It is not necessary to have a thread wait to keep the server alive. As long as there is a reference to the ComputeEngine object in another Java virtual machine, local or remote, the ComputeEngine object will not be shut down or garbage collected. Because the program binds a reference to the ComputeEngine in the registry, it is reachable from a remote client, the registry itself. The RMI system keeps the ComputeEngine's process running. The ComputeEngine is available to accept calls and won't be reclaimed until its binding is removed from the registry and no remote clients hold a remote reference to the ComputeEngine object. The final piece of code in the ComputeEngine.main method handles any exception that might arise. The only checked exception type that could be thrown in the code is RemoteException, either by the UnicastRemoteObject.exportObject invocation or by the registry
76
rebind invocation. In either case, the program cannot do much
more than exit after printing an error message. In some distributed applications, recovering from the failure to make a remote invocation is possible. For example, the application could attempt to retry the operation or choose another server to continue the operation. Creating a Client Program The compute engine is a relatively simple program: it runs tasks that are handed to it. The clients for the compute engine are more complex. A client needs to call the compute engine, but it also has to define the task to be performed by the compute engine. Two separate classes make up the client in our example. The first class, ComputePi, looks up and invokes a Compute object. The second class, Pi, implements the Task interface and defines the work to be done by the compute engine. The job of the Pi class is to compute the value of to some number of decimal places. The non-remote Task interface is defined as follows: package compute; public interface Task { T execute(); }
The code that invokes a Compute object's methods must obtain a reference to that object, create a Task object, and then request that the task be executed. The definition of the task class Pi is shown later. A Pi object is constructed with a single argument, the desired precision of the result. The result of the task execution is a java.math.BigDecimal representing calculated to the specified precision. Here is the source code for client.ComputePi, the main client class: package client; import java.rmi.registry.LocateRegistry;
77
import java.rmi.registry.Registry; import java.math.BigDecimal; import compute.Compute; public class ComputePi { public static void main(String args[]) { if (System.getSecurityManager() == null) { System.setSecurityManager(new SecurityManager()); } try { String name = "Compute"; Registry registry = LocateRegistry.getRegistry(args[0]); Compute comp = (Compute) registry.lookup(name); Pi task = new Pi(Integer.parseInt(args[1])); BigDecimal pi = comp.executeTask(task); System.out.println(pi); } catch (Exception e) { System.err.println("ComputePi exception:"); e.printStackTrace(); } } }
Like the ComputeEngine server, the client begins by installing a security manager. This step is necessary because the process of receiving the server remote object's stub could require downloading class definitions from the server. For RMI to download classes, a security manager must be in force. After installing a security manager, the client constructs a name to use to look up a Compute remote object, using the same name used by ComputeEngine to bind its remote object. Also, the client uses the LocateRegistry.getRegistry API to synthesize a remote reference to the registry on the server's host. The value of the first command-line argument, args[0], is the name of the remote host on which the Compute object runs. The client then invokes the lookup method on the registry to look up the remote object by name in the server host's registry. The particular overload of LocateRegistry.getRegistry used, which has a single String parameter, returns a reference to a registry at the named host and the default registry port, 1099. You must use an overload that has an int parameter if the registry is created on a port other than 1099.
78
Next, the client creates a new Pi object, passing to the Pi constructor the value of the second command-line argument, args[1], parsed as an integer. This argument indicates the number of decimal places to use in the calculation. Finally, the client invokes the executeTask method of the Compute remote object. The object passed into the executeTask invocation returns an object of type BigDecimal, which the program stores in the variable result. Finally, the program prints the result. The following figure depicts the flow of messages among the ComputePi client, the rmiregistry, and the ComputeEngine.
The Pi class implements the Task interface and computes the value of to a specified number of decimal places. For this example, the actual algorithm is unimportant. What is important is that the algorithm is computationally expensive, meaning that you would want to have it executed on a capable server. Here is the source code for client.Pi, the class that implements the Task interface: package client; import compute.Task; import java.io.Serializable; import java.math.BigDecimal; public class Pi implements Task, Serializable { private static final long serialVersionUID = 227L; /** constants used in pi computation */ private static final BigDecimal FOUR = BigDecimal.valueOf(4); /** rounding mode to use during pi computation */ private static final int roundingMode = BigDecimal.ROUND_HALF_EVEN;
79
/** digits of precision after the decimal point */ private final int digits; /** * Construct a task to calculate pi to the specified * precision. */ public Pi(int digits) { this.digits = digits; } /** * Calculate pi. */ public BigDecimal execute() { return computePi(digits); } /** * Compute the value of pi to the specified number of * digits after the decimal point. The value is * computed using Machin's formula: * * pi/4 = 4*arctan(1/5) - arctan(1/239) * * and a power series expansion of arctan(x) to * sufficient precision. */ public static BigDecimal computePi(int digits) { int scale = digits + 5; BigDecimal arctan1_5 = arctan(5, scale); BigDecimal arctan1_239 = arctan(239, scale); BigDecimal pi = arctan1_5.multiply(FOUR).subtract( arctan1_239).multiply(FOUR); return pi.setScale(digits, BigDecimal.ROUND_HALF_UP); } /** * Compute the value, in radians, of the arctangent of * the inverse of the supplied integer to the specified * number of digits after the decimal point. The value * is computed using the power series expansion for the
result = numer; int i = 1; do { numer = numer.divide(invX2, scale, roundingMode); int denom = 2 * i + 1; term = numer.divide(BigDecimal.valueOf(denom), scale, roundingMode); if ((i % 2) != 0) { result = result.subtract(term); } else { result = result.add(term); } i++; } while (term.compareTo(BigDecimal.ZERO) != 0); return result;
}
Note that all serializable classes, whether they implement the Serializable interface directly or indirectly, must declare a private static final field named serialVersionUID to guarantee serialization compatibility between versions. If no previous version of the class has been released, then the value of this field can be any long value, similar to the 227L used by Pi, as long as the value is used consistently in future versions. If a previous version of the class has
81
been released without an explicit serialVersionUID declaration, but serialization compatibility with that version is important, then the default implicitly computed value for the previous version must be used for the value of the new version's explicit declaration. The serialver tool can be run against the previous version to determine the default computed value for it. The most interesting feature of this example is that the Compute implementation object never needs the Pi class's definition until a Pi object is passed in as an argument to the executeTask method. At that point, the code for the class is loaded by RMI into the Compute object's Java virtual machine, the execute method is invoked, and the task's code is executed. The result, which in the case of the Pi task is a BigDecimal object, is handed back to the calling client, where it is used to print the result of the computation. The fact that the supplied Task object computes the value of Pi is irrelevant to the ComputeEngine object. You could also implement a task that, for example, generates a random prime number by using a probabilistic algorithm. That task would also be computationally intensive and therefore a good candidate for passing to the ComputeEngine, but it would require very different code. This code could also be downloaded when the Task object is passed to a Compute object. In just the way that the algorithm for computing is brought in when needed, the code that generates the random prime number would be brought in when needed. The Compute object knows only that each object it receives implements the execute method. The Compute object does not know, and does not need to know, what the implementation does. Compiling and Running the Example Now that the code for the compute engine example has been written, it needs to be compiled and run. Compiling the Example Programs In this section, you learn how to compile the server and the client programs that make up the compute engine example. Running the Example Programs
82
Finally, you run the server and client programs and consequently compute the value of . Compiling the Example Programs In a real-world scenario in which a service such as the compute engine is deployed, a developer would likely create a Java Archive (JAR) file that contains the Compute and Task interfaces for server classes to implement and client programs to use. Next, a developer, perhaps the same developer of the interface JAR file, would write an implementation of the Compute interface and deploy that service on a machine available to clients. Developers of client programs can use the Compute and the Task interfaces, contained in the JAR file, and independently develop a task and client program that uses a Compute service. In this section, you learn how to set up the JAR file, server classes, and client classes. You will see that the client's Pi class will be downloaded to the server at runtime. Also, the Compute and Task interfaces will be downloaded from the server to the registry at runtime. This example separates the interfaces, remote object implementation, and client code into three packages:
compute – Compute and Task interfaces engine – ComputeEngine implementation class client – ComputePi client code and Pi task implementation
First, you need to build the interface JAR file to provide to server and client developers.
Building a JAR File of Interface Classes First, you need to compile the interface source files in the compute package and then build a JAR file that contains their class files. Assume that user waldo has written these interfaces and placed the source files in the directory c:\home\waldo\src\compute on Windows or the directory /home/waldo/src/compute on Solaris OS
83
or Linux. Given these paths, you can use the following commands to compile the interfaces and create the JAR file: Microsoft Windows: cd c:\home\waldo\src javac compute\Compute.java compute\Task.java jar cvf compute.jar compute\*.class
Solaris OS or Linux: cd /home/waldo/src javac compute/Compute.java compute/Task.java jar cvf compute.jar compute/*.class
The jar command displays the following output due to the -v option: added manifest adding: compute/Compute.class(in = 307) (out= 201)(deflated 34%) adding: compute/Task.class(in = 217) (out= 149)(deflated 31%)
Now, you can distribute the compute.jar file to developers of server and client applications so that they can make use of the interfaces. After you build either server-side or client-side classes with the javac compiler, if any of those classes will need to be dynamically downloaded by other Java virtual machines, you must ensure that their class files are placed in a network-accessible location. In this example, for Solaris OS or Linux this location is /home/user/public_html/classes because many web servers allow the accessing of a user's public_html directory through an HTTP URL constructed as http://host/~user/. If your web server does not support this convention, you could use a different location in the web server's hierarchy, or you could use a file URL instead. The file URLs take the form file:/home/user/public_html/classes/ on Solaris OS or Linux and the form file:/c:/home/user/public_html/classes/ on Windows. You may also select another type of URL, as appropriate.
84
The network accessibility of the class files enables the RMI runtime to download code when needed. Rather than defining its own protocol for code downloading, RMI uses URL protocols supported by the Java platform (for example, HTTP) to download code. Note that using a full, heavyweight web server to serve these class files is unnecessary. For example, a simple HTTP server that provides the functionality needed to make classes available for downloading in RMI through HTTP can be found at http://java.sun.com/javase/technologies/core/basic/rmi/class-server.zip.
Building the Server Classes The engine package contains only one server-side implementation class, ComputeEngine, the implementation of the remote interface Compute. Assume that user ann, the developer of the ComputeEngine class, has placed ComputeEngine.java in the directory c:\home\ann\src\engine on Windows or the directory /home/ann/src/engine on Solaris OS or Linux. She is deploying the class files for clients to download in a subdirectory of her public_html directory, c:\home\ann\public_html\classes on Windows or /home/ann/public_html/classes on Solaris OS or Linux. This location is accessible through some web servers as http://host:port/~ann/classes/. The ComputeEngine class depends on the Compute and Task interfaces, which are contained in the compute.jar JAR file. Therefore, you need the compute.jar file in your class path when you build the server classes. Assume that the compute.jar file is located in the directory c:\home\ann\public_html\classes on Windows or the directory /home/ann/public_html/classes on Solaris OS or Linux. Given these paths, you can use the following commands to build the server classes: Microsoft Windows: cd c:\home\ann\src
Solaris OS or Linux: cd /home/ann/src javac -cp /home/ann/public_html/classes/compute.jar engine/ComputeEngine.java
The stub class for ComputeEngine implements the Compute interface, which refers to the Task interface. So, the class definitions for those two interfaces need to be network-accessible for the stub to be received by other Java virtual machines such as the registry's Java virtual machine. The client Java virtual machine will already have these interfaces in its class path, so it does not actually need to download their definitions. The compute.jar file under the public_html directory can serve this purpose. Now, the compute engine is ready to deploy. You could do that now, or you could wait until after you have built the client.
Building the Client Classes The client package contains two classes, ComputePi, the main client program, and Pi, the client's implementation of the Task interface. Assume that user jones, the developer of the client classes, has placed ComputePi.java and Pi.java in the directory c:\home\jones\src\client on Windows or the directory /home/jones/src/client on Solaris OS or Linux. He is deploying the class files for the compute engine to download in a subdirectory of his public_html directory, c:\home\jones\public_html\classes on Windows or /home/jones/public_html/classes on Solaris OS or Linux. This location is accessible through some web servers as http://host:port/~jones/classes/. The client classes depend on the Compute and Task interfaces, which are contained in the compute.jar JAR file. Therefore, you
86
need the compute.jar file in your class path when you build the client classes. Assume that the compute.jar file is located in the directory c:\home\jones\public_html\classes on Windows or the directory /home/jones/public_html/classes on Solaris OS or Linux. Given these paths, you can use the following commands to build the client classes: Microsoft Windows: cd c:\home\jones\src javac -cp c:\home\jones\public_html\classes\compute.jar client\ComputePi.java client\Pi.java mkdir c:\home\jones\public_html\classes\client cp client\Pi.class c:\home\jones\public_html\classes\client
Solaris OS or Linux: cd /home/jones/src javac -cp /home/jones/public_html/classes/compute.jar client/ComputePi.java client/Pi.java mkdir /home/jones/public_html/classes/client cp client/Pi.class /home/jones/public_html/classes/client
Only the Pi class needs to be placed in the directory public_html\classes\client because only the Pi class needs to be available for downloading to the compute engine's Java virtual machine. Now, you can run the server and then the client. Running the Example Programs
A Note About Security The server and client programs run with a security manager installed. When you run either program, you need to specify a security policy file so that the code is granted the security permissions it needs to run. Here is an example policy file to use with the server program: grant codeBase "file:/home/ann/src/" { permission java.security.AllPermission; };
87
Here is an example policy file to use with the client program: grant codeBase "file:/home/jones/src/" { permission java.security.AllPermission; };
For both example policy files, all permissions are granted to the classes in the program's local class path, because the local application code is trusted, but no permissions are granted to code downloaded from other locations. Therefore, the compute engine server restricts the tasks that it executes (whose code is not known to be trusted and might be hostile) from performing any operations that require security permissions. The example client's Pi task does not require any permissions to execute. In this example, the policy file for the server program is named server.policy, and the policy file for the client program is named client.policy.
Starting the Server Before starting the compute engine, you need to start the RMI registry. The RMI registry is a simple server-side bootstrap naming facility that enables remote clients to obtain a reference to an initial remote object. It can be started with the rmiregistry command. Before you execute rmiregistry, you must make sure that the shell or window in which you will run rmiregistry either has no CLASSPATH environment variable set or has a CLASSPATH environment variable that does not include the path to any classes that you want downloaded to clients of your remote objects. To start the registry on the server, execute the rmiregistry command. This command produces no output and is typically run in the background. For this example, the registry is started on the host zaphod. Microsoft Windows (use javaw if start is not available):
88
start rmiregistry
Solaris OS or Linux: rmiregistry &
By default, the registry runs on port 1099. To start the registry on a different port, specify the port number on the command line. Do not forget to unset your CLASSPATH environment variable. Microsoft Windows: start rmiregistry 2001
Solaris OS or Linux: rmiregistry 2001 &
Once the registry is started, you can start the server. You need to make sure that both the compute.jar file and the remote object implementation class are in your class path. When you start the compute engine, you need to specify, using the java.rmi.server.codebase property, where the server's classes are network accessible. In this example, the server-side classes to be made available for downloading are the Compute and Task interfaces, which are available in the compute.jar file in the public_html\classes directory of user ann. The compute engine server is started on the host zaphod, the same host on which the registry was started. Microsoft Windows: java -cp c:\home\ann\src;c:\home\ann\public_html\classes\compute.jar -Djava.rmi.server.codebase=file:/c:/home/ann/public_html/classes/comp ute.jar -Djava.rmi.server.hostname=zaphod.east.sun.com -Djava.security.policy=server.policy engine.ComputeEngine
Solaris OS or Linux: java -cp /home/ann/src:/home/ann/public_html/classes/compute.jar -Djava.rmi.server.codebase=http://zaphod/~ann/classes/compute.jar -Djava.rmi.server.hostname=zaphod.east.sun.com -Djava.security.policy=server.policy
89
engine.ComputeEngine
The above java command defines the following system properties:
The java.rmi.server.codebase property specifies the location, a codebase URL, from which the definitions for classes originating from this server can be downloaded. If the codebase specifies a directory hierarchy (as opposed to a JAR file), you must include a trailing slash at the end of the codebase URL. The java.rmi.server.hostname property specifies the host name or address to put in the stubs for remote objects exported in this Java virtual machine. This value is the host name or address used by clients when they attempt to communicate remote method invocations. By default, the RMI implementation uses the server's IP address as indicated by the java.net.InetAddress.getLocalHost API. However, sometimes, this address is not appropriate for all clients and a fully qualified host name would be more effective. To ensure that RMI uses a host name (or IP address) for the server that is routable from all potential clients, set the java.rmi.server.hostname property. The java.security.policy property is used to specify the policy file that contains the permissions you intend to grant.
Starting the Client Once the registry and the compute engine are running, you can start the client, specifying the following:
The location where the client serves its classes (the Pi class) by using the java.rmi.server.codebase property The java.security.policy property, which is used to specify the security policy file that contains the permissions you intend to grant to various pieces of code
90
As command-line arguments, the host name of the server (so that the client knows where to locate the Compute remote object) and the number of decimal places to use in the calculation
Start the client on another host (a host named ford, for example) as follows: Microsoft Windows: java -cp c:\home\jones\src;c:\home\jones\public_html\classes\compute.jar -Djava.rmi.server.codebase=file:/c:/home/jones/public_html/classes/ -Djava.security.policy=client.policy client.ComputePi zaphod.east.sun.com 45
Solaris OS or Linux: java -cp /home/jones/src:/home/jones/public_html/classes/compute.jar -Djava.rmi.server.codebase=http://ford/~jones/classes/ -Djava.security.policy=client.policy client.ComputePi zaphod.east.sun.com 45
Note that the class path is set on the command line so that the interpreter can find the client classes and the JAR file containing the interfaces. Also note that the value of the java.rmi.server.codebase property, which specifies a directory hierarchy, ends with a trailing slash. After you start the client, the following output is displayed: 3.141592653589793238462643383279502884197169399
The following figure illustrates where the rmiregistry, the ComputeEngine server, and the ComputePi client obtain classes during program execution.
91
When the ComputeEngine server binds its remote object reference in the registry, the registry downloads the Compute and Task interfaces on which the stub class depends. These classes are downloaded from either the ComputeEngine server's web server or file system, depending on the type of codebase URL used when starting the server. Because the ComputePi client has both the Compute and the Task interfaces available in its class path, it loads their definitions from its class path, not from the server's codebase. Finally, the Pi class is loaded into the ComputeEngine server's Java virtual machine when the Pi object is passed in the executeTask remote call to the ComputeEngine object. The Pi class is loaded by the server from either the client's web server or file system, depending on the type of codebase URL used when starting the client.
Swings
What is Swing? To create a Java program with a graphical user interface (GUI), you'll want to learn about Swing. The Swing toolkit includes a rich set of components for building GUIs and adding interactivity to Java applications. Swing includes all the components you would expect from a modern toolkit: table controls, list controls, tree controls, buttons, and labels.
92
Swing is far from a simple component toolkit, however. It includes rich undo support, a highly customizable text package, integrated internationalization and accessibility support. To truly leverage the cross-platform capabilities of the Java platform, Swing supports numerous look and feels, including the ability to create your own look and feel. The ability to create a custom look and feel is made easier with Synth, a look and feel specifically designed to be customized. Swing wouldn't be a component toolkit without the basic user interface primitives such as drag and drop, event handling, customizable painting, and window management. Swing is part of the Java Foundation Classes (JFC). The JFC also include other features important to a GUI program, such as the ability to add rich graphics functionality and the ability to create a program that can work in different languages and by users with different input devices. The following list shows some of the features that Swing and the Java Foundation Classes provide. Swing GUI Components The Swing toolkit includes a rich array of components: from basic components, such as buttons and check boxes, to rich and complex components, such as tables and text. Even deceptively simple components, such as text fields, offer sophisticated functionality, such as formatted text input or password field behavior. There are file browsers and dialogs to suit most needs, and if not, customization is possible. If none of Swing's provided components are exactly what you need, you can leverage the basic Swing component functionality to create your own. Java 2D API To make your application stand out; convey information visually; or add figures, images, or animation to your GUI, you'll want to use the Java 2DTM API. Because Swing is built on the 2D package,
93
it's trivial to make use of 2D within Swing components. Adding images, drop shadows, compositing — it's easy with Java 2D. Pluggable Look-and-Feel Support Any program that uses Swing components has a choice of look and feel. The JFC classes shipped by Sun and Apple provide a look and feel that matches that of the platform. The Synth package allows you to create your own look and feel. The GTK+ look and feel makes hundreds of existing look and feels available to Swing programs. A program can specify the look and feel of the platform it is running on, or it can specify to always use the Java look and feel, and without recompiling, it will just work. Or, you can ignore the issue and let the UI manager sort it out. Data Transfer Data transfer, via cut, copy, paste, and drag and drop, is essential to almost any application. Support for data transfer is built into Swing and works between Swing components within an application, between Java applications, and between Java and native applications. Internationalization This feature allows developers to build applications that can interact with users worldwide in their own languages and cultural conventions. Applications can be created that accept input in languages that use thousands of different characters, such as Japanese, Chinese, or Korean. Swing's layout managers make it easy to honor a particular orientation required by the UI. For example, the UI will appear right to left in a locale where the text flows right to left. This support is automatic: You need only code the UI once and then it will work for left to right and right to left, as well as honor the appropriate size of components that change as you localize the text.
94
Accessibility API People with disabilities use special software — assistive technologies — that mediates the user experience for them. Such software needs to obtain a wealth of information about the running application in order to represent it in alternate media: for a screen reader to read the screen with synthetic speech or render it via a Braille display, for a screen magnifier to track the caret and keyboard focus, for onscreen keyboards to present dynamic keyboards of the menu choices and toolbar items and dialog controls, and for voice control systems to know what the user can control with his or her voice. The accessibility API enables these assistive technologies to get the information they need, and to programmatically manipulate the elements that make up the graphical user interface. Undo Framework API Swing's undo framework allows developers to provide support for undo and redo. Undo support is built in to Swing's text component. For other components, Swing supports an unlimited number of actions to undo and redo, and is easily adapted to an application. For example, you could easily enable undo to add and remove elements from a table. Flexible Deployment Support If you want your program to run within a browser window, you can create it as an applet and run it using Java Plug-in, which supports a variety of browsers, such as Internet Explorer, Firefox, and Safari. If you want to create a program that can be launched from a browser, you can do this with Java Web Start. Of course, your application can also run outside of browser as a standard desktop application. For more information on deploying an application, see the Deployment trail in this tutorial.
95
This trail provides an overview of Swing capabilities, beginning with a demo that showcases many of these features. When you are ready to begin coding, the Creating a GUI with JFC/Swing trail provides the programming techniques to take advantage of these features. Next, a demo shows many of these features. Using Top-Level Containers As we mentioned before, Swing provides three generally useful top-level container classes: JFrame, JDialog, and JApplet. When using these classes, you should keep these facts in mind:
To appear onscreen, every GUI component must be part of a containment hierarchy. A containment hierarchy is a tree of components that has a top-level container as its root. We'll show you one in a bit. Each GUI component can be contained only once. If a component is already in a container and you try to add it to another container, the component will be removed from the first container and then added to the second. Each top-level container has a content pane that, generally speaking, contains (directly or indirectly) the visible components in that top-level container's GUI. You can optionally add a menu bar to a top-level container. The menu bar is by convention positioned within the toplevel container, but outside the content pane. Some look and feels, such as the Mac OS look and feel, give you the option of placing the menu bar in another place more appropriate for the look and feel, such as at the top of the screen.
Here's a picture of a frame created by an application. The frame contains a green menu bar (with no menus) and, in the frame's content pane, a large blank, yellow label.
You can find the entire source for this example in TopLevelDemo.java. Although the example uses a JFrame in a standalone application, the same concepts apply to JApplets and JDialogs. Here's the containment hierarchy for this example's GUI:
As the ellipses imply, we left some details out of this diagram. We reveal the missing details a bit later. Here are the topics this section discusses:
Top-Level Containers and Containment Hierarchies Adding Components to the Content Pane Adding a Menu Bar The Root Pane (a.k.a. The Missing Details)
97
Top-Level Containers and Containment Hierarchies Each program that uses Swing components has at least one toplevel container. This top-level container is the root of a containment hierarchy — the hierarchy that contains all of the Swing components that appear inside the top-level container. As a rule, a standalone application with a Swing-based GUI has at least one containment hierarchy with a JFrame as its root. For example, if an application has one main window and two dialogs, then the application has three containment hierarchies, and thus three top-level containers. One containment hierarchy has a JFrame as its root, and each of the other two has a JDialog object as its root. A Swing-based applet has at least one containment hierarchy, exactly one of which is rooted by a JApplet object. For example, an applet that brings up a dialog has two containment hierarchies. The components in the browser window are in a containment hierarchy rooted by a JApplet object. The dialog has a containment hierarchy rooted by a JDialog object.
Adding Components to the Content Pane Here's the code that the preceding example uses to get a frame's content pane and add the yellow label to it: frame.getContentPane().add(yellowLabel, BorderLayout.CENTER);
As the code shows, you find the content pane of a top-level container by calling the getContentPane method. The default content pane is a simple intermediate container that inherits from JComponent, and that uses a BorderLayout as its layout manager. It's easy to customize the content pane — setting the layout manager or adding a border, for example. However, there is one tiny gotcha. The getContentPane method returns a Container object, not a JComponent object. This means that if you want to take advantage of the content pane's JComponent features, you
98
need to either typecast the return value or create your own component to be the content pane. Our examples generally take the second approach, since it's a little cleaner. Another approach we sometimes take is to simply add a customized component to the content pane, covering the content pane completely. Note that the default layout manager for JPanel is FlowLayout; you'll probably want to change it. To make a component the content pane, use the top-level container's setContentPane method. For example: //Create a panel and add components to it. JPanel contentPane = new JPanel(new BorderLayout()); contentPane.setBorder(someBorder); contentPane.add(someComponent, BorderLayout.CENTER); contentPane.add(anotherComponent, BorderLayout.PAGE_END); topLevelContainer.setContentPane(contentPane);
Note: As a convenience, the add method and its variants, remove and setLayout have been overridden to forward to the contentPane as necessary. This means you can write frame.add(child);
and the child will be added to the contentPane. Note that only these three methods do this. This means that getLayout() will not return the layout set with setLayout().
Adding a Menu Bar In theory, all top-level containers can hold a menu bar. In practice, however, menu bars usually appear only in frames and applets. To add a menu bar to a top-level container, create a JMenuBar object, populate it with menus, and then call setJMenuBar. The TopLevelDemo adds a menu bar to its frame with this code: frame.setJMenuBar(greenMenuBar);
99
For more information about implementing menus and menu bars, see How to Use Menus.
The Root Pane Each top-level container relies on a reclusive intermediate container called the root pane. The root pane manages the content pane and the menu bar, along with a couple of other containers. You generally don't need to know about root panes to use Swing components. However, if you ever need to intercept mouse clicks or paint over multiple components, you should get acquainted with root panes. Here's a list of the components that a root pane provides to a frame (and to every other top-level container):
We've already told you about the content pane and the optional menu bar. The two other components that a root pane adds are a layered pane and a glass pane. The layered pane contains the menu bar and content pane, and enables Z-ordering of other components. The glass pane is often used to intercept input events occuring over the top-level container, and can also be used to paint over multiple components. For more details, see How to Use Root Panes.
100
JAAS Internationalization
Introduction Internationalization is the process of designing an application so that it can be adapted to various languages and regions without engineering changes. Sometimes the term internationalization is abbreviated as i18n, because there are 18 letters between the first "i" and the last "n." An internationalized program has the following characteristics:
With the addition of localized data, the same executable can run worldwide. Textual elements, such as status messages and the GUI component labels, are not hardcoded in the program. Instead they are stored outside the source code and retrieved dynamically. Support for new languages does not require recompilation. Culturally-dependent data, such as dates and currencies, appear in formats that conform to the end user's region and language. It can be localized quickly.
Localization is the process of adapting software for a specific region or language by adding locale-specific components and translating text. The term localization is often abbreviated as l10n, because there are 10 letters between the "l" and the "n." The primary task of localization is translating the user interface elements and documentation. Localization involves not only changing the language interaction, but also other relevant changes such as display of numbers, dates, currency, and so on. Other types of data, such as sounds and images, may require localization if they are culturally sensitive. The better internationalized an application is, the easier it is to localize it for a particular language and character encoding scheme.
101
Internationalization may seem a bit daunting at first. Reading the following sections will help ease you into the subject. Before Internationalization Suppose that you've written a program that displays three messages, as follows: public class NotI18N { static public void main(String[] args) { System.out.println("Hello."); System.out.println("How are you?"); System.out.println("Goodbye."); }
}
You've decided that this program needs to display these same messages for people living in France and Germany. Unfortunately your programming staff is not multilingual, so you'll need help translating the messages into French and German. Since the translators aren't programmers, you'll have to move the messages out of the source code and into text files that the translators can edit. Also, the program must be flexible enough so that it can display the messages in other languages, but right now no one knows what those languages will be. It looks like the program needs to be internationalized. After Internationalization The source code for the internationalized program follows. Notice that the text of the messages is not hardcoded. import java.util.*; public class I18NSample { static public void main(String[] args) { String language; String country;
102
if (args.length != 2) { language = new String("en"); country = new String("US"); } else { language = new String(args[0]); country = new String(args[1]); } Locale currentLocale; ResourceBundle messages; currentLocale = new Locale(language, country); messages = ResourceBundle.getBundle("MessagesBundle", currentLocale); System.out.println(messages.getString("greetings")); System.out.println(messages.getString("inquiry")); System.out.println(messages.getString("farewell")); }
}
To compile and run this program, you need these source files:
Running the Sample Program The internationalized program is flexible; it allows the end user to specify a language and a country on the command line. In the following example the language code is fr (French) and the country code is FR (France), so the program displays the messages in French: % java I18NSample fr FR Bonjour. Comment allez-vous? Au revoir.
103
In the next example the language code is en (English) and the country code is US (United States) so the program displays the messages in English: % java I18NSample en US Hello. How are you? Goodbye.
Internationalizing the Sample Program If you look at the internationalized source code, you'll notice that the hardcoded English messages have been removed. Because the messages are no longer hardcoded and because the language code is specified at run time, the same executable can be distributed worldwide. No recompilation is required for localization. The program has been internationalized. You may be wondering what happened to the text of the messages or what the language and country codes mean. Don't worry. You'll learn about these concepts as you step through the process of internationalizing the sample program.
1. Create the Properties Files A properties file stores information about the characteristics of a program or environment. A properties file is in plain-text format. You can create the file with just about any text editor. In the example the properties files store the translatable text of the messages to be displayed. Before the program was internationalized, the English version of this text was hardcoded in the System.out.println statements. The default properties file, which is called MessagesBundle.properties, contains the following lines: greetings = Hello farewell = Goodbye inquiry = How are you?
104
Now that the messages are in a properties file, they can be translated into various languages. No changes to the source code are required. The French translator has created a properties file called MessagesBundle_fr_FR.properties, which contains these lines: greetings = Bonjour. farewell = Au revoir. inquiry = Comment allez-vous?
Notice that the values to the right side of the equal sign have been translated but that the keys on the left side have not been changed. These keys must not change, because they will be referenced when your program fetches the translated text. The name of the properties file is important. For example, the name of the MessagesBundle_fr_FR.properties file contains the fr language code and the FR country code. These codes are also used when creating a Locale object.
2. Define the Locale The Locale object identifies a particular language and country. The following statement defines a Locale for which the language is English and the country is the United States: aLocale = new Locale("en","US");
The next example creates Locale objects for the French language in Canada and in France: caLocale = new Locale("fr","CA"); frLocale = new Locale("fr","FR");
The program is flexible. Instead of using hardcoded language and country codes, the program gets them from the command line at run time: String language = new String(args[0]); String country = new String(args[1]); currentLocale = new Locale(language, country);
105
Locale objects are only identifiers. After defining a Locale, you
pass it to other objects that perform useful tasks, such as formatting dates and numbers. These objects are locale-sensitive because their behavior varies according to Locale. A ResourceBundle is an example of a locale-sensitive object.
3. Create a ResourceBundle ResourceBundle objects contain locale-specific objects. You use ResourceBundle objects to isolate locale-sensitive data, such as translatable text. In the sample program the ResourceBundle is
backed by the properties files that contain the message text we want to display. The ResourceBundle is created as follows: messages = ResourceBundle.getBundle("MessagesBundle", currentLocale);
The arguments passed to the getBundle method identify which properties file will be accessed. The first argument, MessagesBundle, refers to this family of properties files: MessagesBundle_en_US.properties MessagesBundle_fr_FR.properties MessagesBundle_de_DE.properties
The Locale, which is the second argument of getBundle, specifies which of the MessagesBundle files is chosen. When the Locale was created, the language code and the country code were passed to its constructor. Note that the language and country codes follow MessagesBundle in the names of the properties files. Now all you have to do is get the translated messages from the ResourceBundle.
4. Fetch the Text from the ResourceBundle The properties files contain key-value pairs. The values consist of the translated text that the program will display. You specify the
106
keys when fetching the translated messages from the ResourceBundle with the getString method. For example, to retrieve the message identified by the greetings key, you invoke getString as follows: String msg1 = messages.getString("greetings"); The sample program uses the key greetings because it reflects
the content of the message, but it could have used another String, such as s1 or msg1. Just remember that the key is hardcoded in the program and it must be present in the properties files. If your translators accidentally modify the keys in the properties files, getString won't be able to find the messages.
Conclusion That's it. As you can see, internationalizing a program isn't too difficult. It requires some planning and a little extra coding, but the benefits are enormous. To provide you with an overview of the internationalization process, the sample program in this lesson was intentionally kept simple. As you read the lessons that follow, you'll learn about the more advanced internationalization features of the Java programming language. JUnit JNDI
Java Naming and Directory Interface(TM). This trail describes JNDITM (Java Naming and Directory Interface) an API to access the directory and naming services. Here you learn about the basic naming and directory services and how to use JNDI to write simple applications to use these services. The most popular directory service LDAP is used to demostrate the use of JNDI to access the directory services. Naming and Directory Concepts
107
Naming Concepts A fundamental facility in any computing system is the naming service--the means by which names are associated with objects and objects are found based on their names. When using almost any computer program or system, you are always naming one object or another. For example, when you use an electronic mail system, you must provide the name of the recipient. To access a file in the computer, you must supply its name. A naming service allows you to look up an object given its name.
A naming service's primary function is to map people friendly names to objects, such as addresses, identifiers, or objects typically used by computer programs. For example, the Internet Domain Name System (DNS) maps machine names to IP Addresses: www.sun.com ==> 192.9.48.5.
A file system maps a filename to a file reference that a program can use to access the contents of the file. c:\bin\autoexec.bat ==> File Reference
These two examples also illustrate the wide range of scale at which naming services exist--from naming an object on the Internet to naming a file on the local file system.
Names To look up an object in a naming system, you supply it the name of the object. The naming system determines the syntax that the name must follow. This syntax is sometimes called the naming system's naming convention. A name is made up components. A name's representation consist of a component separator marking the components of the name.
108
Naming System UNIXTM file system DNS LDAP
Component Seperator
Names
"/"
/usr/hello
"."
sales.Wiz.COM cn=Rosanna Lee, o=Sun, c=US
"," and "="
The UNIXTM file system's naming convention is that a file is named from its path relative to the root of the file system, with each component in the path separated from left to right using the forward slash character ("/"). The UNIX pathname, /usr/hello, for example, names a file hello in the file directory usr, which is located in the root of the file system. DNS naming convention calls for components in the DNS name to be ordered from right to left and delimited by the dot character ("."). Thus the DNS name sales.Wiz.COM names a DNS entry with the name sales, relative to the DNS entry Wiz.COM. The DNS entry Wiz.COM, in turn, names an entry with the name Wiz in the COM entry. The Lightweight Directory Access Protocol (LDAP) naming convention orders components from right to left, delimited by the comma character (","). Thus the LDAP name cn=Rosanna Lee, o=Sun, c=US names an LDAP entry cn=Rosanna Lee, relative to the entry o=Sun, which in turn, is relative to c=us. LDAP has the further rule that each component of the name must be a name/value pair with the name and value separated by an equals character ("=").
Bindings The association of a name with an object is called a binding. A file name is bound to a file.
109
The DNS contains bindings that map machine names to IP addresses. An LDAP name is bound to an LDAP entry.
References and Addresses Depending on the naming service, some objects cannot be stored directly by the naming service; that is, a copy of the object cannot be placed inside the naming service. Instead, they must be stored by reference; that is, a pointer or reference to the object is placed inside the naming service. A reference represents information about how to access an object. Typically, it is a compact representation that can be used to communicate with the object, while the object itself might contain more state information. Using the reference, you can contact the object and obtain more information about the object. For example, an airplane object might contain a list of the airplane's passengers and crew, its flight plan, and fuel and instrument status, and its flight number and departure time. By contrast, an airplane object reference might contain only its flight number and departure time. The reference is a much more compact representation of information about the airplane object and can be used to obtain additional information. A file object, for example, is accessed using a file reference. A printer object, for example, might contain the state of the printer, such as its current queue and the amount of paper in the paper tray. A printer object reference, on the other hand, might contain only information on how to reach the printer, such as its print server name and printing protocol. Although in general a reference can contain any arbitrary information, it is useful to refer to its contents as addresses (or communication end points): specific information about how to access the object.
110
For simplicity, this tutorial uses "object" to refer to both objects and object references when a distinction between the two is not required.
Context A context is a set of name-to-object bindings. Every context has an associated naming convention. A context always provides a lookup (resolution) operation that returns the object, it typically also provides operations such as those for binding names, unbinding names, and listing bound names. A name in one context object can be bound to another context object (called a subcontext) that has the same naming convention.
A file directory, such as /usr, in the UNIX file system represents a context. A file directory named relative to another file directory represents a subcontext (UNIX users refer to this as a subdirectory). That is, in a file directory /usr/bin, the directory bin is a subcontext of usr. A DNS domain, such as COM, represents a context. A DNS domain named relative to another DNS domain represents a subcontext. For the DNS domain Sun.COM, the DNS domain Sun is a subcontext of COM. Finally, an LDAP entry, such as c=us, represents a context. An LDAP entry named relative to another LDAP entry represents a subcontext. For the LDAP entry o=sun,c=us, the entry o=sun is a subcontext of c=us.
Naming Systems and Namespaces A naming system is a connected set of contexts of the same type (they have the same naming convention) and provides a common set of operations.
111
A system that implements the DNS is a naming system. A system that communicates using the LDAP is a naming system. A naming system provides a naming service to its customers for performing naming-related operations. A naming service is accessed through its own interface. The DNS offers a naming service that maps machine names to IP addresses. LDAP offers a naming service that maps LDAP names to LDAP entries. A file system offers a naming service that maps filenames to files and directories. A namespace is the set of all possible names in a naming system. The UNIX file system has a namespace consisting of all of the names of files and directories in that file system. The DNS namespace contains names of DNS domains and entries. The LDAP namespace contains names of LDAP entries. Directory Concepts Many naming services are extended with a directory service. A directory service associates names with objects and also associates such objects with attributes. directory service = naming service + objects containing attributes You not only can look up an object by its name but also get the object's attributes or search for the object based on its attributes.
An example is the telephone company's directory service. It maps a subscriber's name to his address and phone number. A computer's directory service is very much like a telephone company's directory service in that both can be used to store information such as telephone numbers and addresses. The computer's directory service is much more powerful, however, because it is available online and can be used to store a variety
112
of information that can be utilized by users, programs, and even the computer itself and other computers. A directory object represents an object in a computing environment. A directory object can be used, for example, to represent a printer, a person, a computer, or a network. A directory object contains attributes that describe the object that it represents.
Attributes A directory object can have attributes. For example, a printer might be represented by a directory object that has as attributes its speed, resolution, and color. A user might be represented by a directory object that has as attributes the user's e-mail address, various telephone numbers, postal mail address, and computer account information. An attribute has an attribute identifier and a set of attribute values. An attribute identifier is a token that identifies an attribute independent of its values. For example, two different computer accounts might have a "mail" attribute; "mail" is the attribute identifier. An attribute value is the contents of the attribute. The email address, for example, might have: Attribute Identifier : Attribute Value mail [email protected]
Directories and Directory Services A directory is a connected set of directory objects. A directory service is a service that provides operations for creating, adding, removing, and modifying the attributes associated with objects in a directory. The service is accessed through its own interface. Many examples of directory services are possible. Network Information Service (NIS)
113
NIS is a directory service available on the Unix operating system for storing system-related information, such as that relating to machines, networks, printers, and users. Sun Java Directory Server The Sun Java Directory Server is a general-purpose directory service based on the Internet standard LDAP Novell Directory Service (NDS) NDS is a directory service from Novell that provides information about many networking services, such as the file and print services.
Search Service You can look up a directory object by supplying its name to the directory service. Alternatively, many directories, such as those based on the LDAP, support the notion of searches. When you search, you can supply not a name but a query consisting of a logical expression in which you specify the attributes that the object or objects must have. The query is called a search filter. This style of searching is sometimes called reverse lookup or content-based searching. The directory service searches for and returns the objects that satisfy the search filter. For example, you can query the directory service to find:
all users that have the attribute "age" greater than 40 years. all machines whose IP address starts with "192.113.50".
Combining Naming and Directory Services Directories often arrange their objects in a hierarchy. For example, the LDAP arranges all directory objects in a tree, called a directory information tree (DIT). Within the DIT, an organization object, for example, might contain group objects that might in turn contain person objects. When directory objects are arranged in this way, they play the role of naming contexts in addition to that of containers of attributes. Overview of JNDI
114
The Java Naming and Directory InterfaceTM (JNDI) is an application programming interface (API) that provides naming and directory functionality to applications written using the JavaTM programming language. It is defined to be independent of any specific directory service implementation. Thus a variety of directories -new, emerging, and already deployed can be accessed in a common way.
Architecture The JNDI architecture consists of an API and a service provider interface (SPI). Java applications use the JNDI API to access a variety of naming and directory services. The SPI enables a variety of naming and directory services to be plugged in transparently, thereby allowing the Java application using the JNDI API to access their services. See the following figure.
Packaging JNDI is included in the Java SE Platform. To use the JNDI, you must have the JNDI classes and one or more service providers. The JDK includes service providers for the following naming/directory services:
Lightweight Directory Access Protocol (LDAP) Common Object Request Broker Architecture (CORBA) Common Object Services (COS) name service Java Remote Method Invocation (RMI) Registry Domain Name Service (DNS)
Other service providers can be downloaded from the JNDI Web site or obtained from other vendors. The JNDI is divided into five packages:
The next part of the lesson has a brief description of the JNDI packages. Naming Package The javax.naming package contains classes and interfaces for accessing naming services.
Context The javax.naming package defines a Context interface, which is the core interface for looking up, binding/unbinding, renaming objects and creating and destroying subcontexts. Lookup The most commonly used operation is lookup(). You supply lookup() the name of the object you want to look up, and it returns the object bound to that name. Bindings listBindings() returns an enumeration of name-to-object bindings. A binding is a tuple containing the name of the bound object, the name of the object's class, and the object itself. List list()is similar to listBindings(), except that it returns an enumeration of names containing an object's name and the name of the object's class. list() is useful for applications such as browsers that want to discover information about the objects bound within a context but that don't need all of the actual objects. Although listBindings() provides all of the same information, it is potentially a much more expensive operation. Name
116
Name is an interface that represents a generic name--an
ordered sequence of zero or more components. The Naming Systems use this interface to define the names that follow its conventions as described in the Naming and Directory Concepts lesson. References Objects are stored in naming and directory services in different ways. A reference might be a very compact representation of an object. The JNDI defines the Reference class to represent reference. A reference contains information on how to construct a copy of the object. The JNDI will attempt to turn references looked up from the directory into the Java objects that they represent so that JNDI clients have the illusion that what is stored in the directory are Java objects.
The Initial Context In the JNDI, all naming and directory operations are performed relative to a context. There are no absolute roots. Therefore the JNDI defines an InitialContext, which provides a starting point for naming and directory operations. Once you have an initial context, you can use it to look up other contexts and objects.
Exceptions The JNDI defines a class hierarchy for exceptions that can be thrown in the course of performing naming and directory operations. The root of this class hierarchy is NamingException. Programs interested in dealing with a particular exception can catch the corresponding subclass of the exception. Otherwise, they should catch NamingException. Directory and LDAP Packages
Directory Package The javax.naming package to provide functionality for accessing directory services in addition to naming services. This package
117
allows applications to retrieve associated with objects stored in the directory and to search for objects using specified attributes.
The Directory Context The DirContextinterface represents a directory context. DirContext also behaves as a naming context by extending the getAttributes()to retrieve the attributes associated with a directory entry (for which you supply the name). Attributes are modified using modifyAttributes()method. Other overloaded forms of search()support more sophisticated search filters.
LDAP Package The javax.naming.ldap package contains classes and interfaces for using features that are specific to the LDAP v3 that are not already covered by the more generic javax.naming.directory package. In fact, most JNDI applications that use the LDAP will find the javax.naming.directory package sufficient and will not need to use the javax.naming.ldap package at all. This package is primarily for those applications that need to use "extended" operations, controls, or unsolicited notifications. "Extended" Operation In addition to specifying well defined operations such as search and modify, the LDAP v3 (RFC 2251) specifies a way to transmit yet-to-be defined operations between the LDAP client and the server. These operations are called "extended" operations. An "extended" operation may be defined by a standards organization such as the Internet Engineering Task Force (IETF) or by a vendor. Controls The LDAP v3 allows any request or response to be augmented by yet-to-be defined modifiers, called controls . A control sent with a request is a request control and a control sent with a response is a response control . A control may be defined by a standards organization such as the IETF or by a vendor. Request controls and response controls
118
are not necessarily paired, that is, there need not be a response control for each request control sent, and vice versa. Unsolicited Notifications In addition to the normal request/response style of interaction between the client and server, the LDAP v3 also specifies unsolicited notifications--messages that are sent from the server to the client asynchronously and not in response to any client request.
The LDAP Context The LdapContextinterface represents a context for performing "extended" operations, sending request controls, and receiving response controls. Examples of how to use these features are described in the JNDI Tutorial's Controls and Extensions lesson. Event and Service Provider Packages
Event Package The javax.naming.event package contains classes and interfaces for supporting event notification in naming and directory services. Event notification is described in detail in the trail. Events A NamingEvent represents an event that is generated by a naming/directory service. The event contains a type that identifies the type of event. For example, event types are categorized into those that affect the namespace, such as "object added," and those that do not, such as "object changed." Listeners A NamingListener is an object that listens for NamingEvents. Each category of event type has a corresponding type of NamingListener. For example, a NamespaceChangeListener represents a listener interested in namespace change events and an ObjectChangeListener represents a listener interested in object change events.
119
To receive event notifications, a listener must be registered with either an EventContext or an EventDirContext. Once registered, the listener will receive event notifications when the corresponding changes occur in the naming/directory service. The details about Event Notification can be found in the JNDI Tutorial
Service Provider Package The javax.naming.spi package provides the means by which developers of different naming/directory service providers can develop and hook up their implementations so that the corresponding services are accessible from applications that use the JNDI. Plug-In Architecture The javax.naming.spi package allows different implementations to be plugged in dynamically. These implementations include those for the initial context and for contexts that can be reached from the initial context. Java Object Support The javax.naming.spi package supports implementors of lookup and related methods to return Java objects that are natural and intuitive for the Java programmer. For example, if you look up a printer name from the directory, then you likely would expect to get back a printer object on which to operate. This support is provided in the form of object factories. This package also provides support for doing the reverse. That is, implementors of Context.bind() and related methods can accept Java objects and store the objects in a format acceptable to the underlying naming/directory service. This support is provided in the form of state factories. Multiple Naming Systems (Federation) JNDI operations allow applications to supply names that span multiple naming systems. In the process of completing an operation, one service provider might need to interact
120
with another service provider, for example to pass on the operation to be continued in the next naming system. This package provides support for different providers to cooperate to complete JNDI operations. The details about the Service Provider mechanism can be found in the JNDI Tutorial
121
J2EE Servlets JSP EJB Struts JMS Hibernate SAX DOM Design Patterns Session façade Front Controller DAO Chain of Responsibilities Composition Aggregation Abstract Factory Factory method Bridge Singleton Builder Iterate Observer
122
State Strategy Visitor Flyweight Proxy Router Translation Web Services SOAP UDDI WSDL Apache Axis XML Technologies XML DDL XSL Link Path XQuery Database Oracle 9i(SQLPL/SQL) DB2 Application Servers
123
WebSphere Application Server 6.1 WebLogic 9.1 JBoss 4.1.2 Apache Tomcat5.5 UML tools Rational UML Modeling tool Web Design HTML Java Script CSS AJAX Methodologies OOAD OODB SAD Tools Eclispe3.2 Ant Maven Batch Script Shell script Strategies Requirement/Request Analysis
124
Deployment and Configuration Performance Tuning and Review. Configuration Tools Rational Clear Case Operating Systems Windows Unix Sub Topics Extension Mechanism
for Support of Optional Packages Documentation Contents Optional packages are packages of classes (and any associated native code) that application developers can use to extend the functionality of the core platform. The extension mechanism allows the Java virtual machine (VM) to use the classes of the optional extension in much the same way as the VM uses classes in the Java Platform. The extension mechanism also provides a way for needed optional packages to be retrieved from specified URLs when they are not already installed in the JDK or JRE. Overview * Overview - What optional packages are and how to use them. API Specification The following classes play a role in the extension mechanism:
See also the API Details section of the mechanism specification for notes on these APIs. Tutorials and Programmer's Guides Located on the Java Software website: * The Extension Mechanism trail of the Java Tutorial. API Enhancements * Enhancements to the extension mechanism in version 1.3 included an extended set of manifest attributes that can be used for checking vendor and versioning information of installed optional packages. If an applet requires an optional package that isn't installed, or that is installed but has the wrong version number or is not from the appropriate vendor, the Java Plug-in can download the needed extension from a specified URL. For more information, see Optional-Package Versioning. More Information
Extension Mechanism Architecture - Notes on the extension mechanism API and how optional packages use the Jar file format.
Introduction Note: Optional packages are the new name for what used to be known as standard extensions. The "extension mechanism" is that functionality of the JDK™ and JRE™ that supports the use of optional packages. This document describes the mechanism provided by the Java™ platform for handling optional packages. An optional package is a group of packages housed in one or more JAR files that implement an API that extends the Java platform. Optional package classes extend the platform in the sense that the virtual machine can find and load them without their being on the class path, much as if they were classes in the platform's core API. Since optional packages extend the platform's core API, their use should be judiciously applied. Most commonly they are used for well standardized interfaces such as those defined by the Java Community ProcessSM, although it may also be appropriate for site wide interfaces. Optional packages are rarely appropriate for interfaces used by a single, or small set of applications. Furthermore, since the symbols defined by installed optional packages will be visible in all Java processes, care should be taken to ensure that all visible symbols follow the appropriate "reverse domain name" and "class hierarchy" conventions. For example, com.mycompany.MyClass.
127
An implementation of an optional package may consist of code written in the Java programming language and, less commonly, platform-specific native code. In addition, it may include properties, localization catalogs, images, serialized data, and other resources specific to the optional package. Support for optional packages in browsers such as Internet Explorer and Netscape Navigator is available through the Java Plug-in. A optional package is an implementation of an open, standard API (examples of optional packages from Sun are JavaServlet, Java3D, JavaManagement). Most optional packages are rooted in the javax.* namespace, although there may be exceptions.
The Extension Mechanism Architecture The extension mechanism is designed to contain the following elements:
an optional package or application packaged as a JAR file can declare dependencies on other JAR files, thus allowing an application to consist of multiple modules, and, the class loading mechanism is augmented to search installed optional packages (and other libraries) for classes, and, if that fails, to search along an application-specified path for classes.
Applications must therefore, in general, be prepared to specify and supply the optional packages (and, more generally, libraries) that it needs. The system will prefer installed copies of optional packages (and libraries) if they exist; otherwise, it will delegate to the class loader of the application to find and load the referenced optional package (and library) classes.
128
This architecture, since it allows applications, applets and servlets to extend their own class path, also permits packaging and deploying these as multiple JAR files. Each optional package or application consists of at least one JAR file containing an optional manifest, code and assorted resources. As described below, this primary JAR file can also include additional information in its manifest to describe dependencies on other JAR files. The jar command line tool included with the JDK provides a convenient means of packaging optional packages. (See the reference pages for the jar tool: [Microsoft Windows] [Solaris™ Operating System (Solaris OS), Linux]) An optional package or application may refer to additional JAR files which will be referenced from the primary JAR, and these can optionally contain their own dependency information as well. Packages comprising optional packages should be named per the standard package naming conventions when implementing optional packages. These conventions are outlined in The Java Language Specification, but the requirement that the domain prefix be specified in all upper case letters has been removed. For example, the package name com.sun.server is an accepted alternative to COM.sun.server. Unique package naming is recommended in order to avoid conflicts, because applications and optional packages may share the same class loader.
Optional Package Deployment An optional package may either be bundled with an application or installed in the JRE for use by all applications. Bundled optional packages are provided at the same code base as the application and will automatically be downloaded in the case of network applications (applets). For this reason, bundled optional packages are often called download optional packages. When the manifest of a bundled optional package's JAR file contains version information and the JAR is signed, it can be installed into
129
the extensions directory of the JRE which downloads it (see Deploying Java Extensions). Installed optional packages are loaded when first used and will be shared by all applications using the same JRE. When packaging optional packages, the JAR file manifest can be used to identify vendor and version information (see Package Version Identification). Classes for installed optional packages are shared by all code in the same virtual machine. Thus, installed optional packages are similar to the platform's core classes (in rt.jar), but with an associated class loader and a pre-configured security policy as described below. Classes for bundled optional packages are private to the class loader of the application, applet or servlet. In the case of network applications such as applets, these optional packages will be automatically downloaded as needed. Since class loaders are currently associated with a codebase, this permits multiple applets originating from the same codebase to share implementations (JARs). However, signed bundled optional packages with version information as described above are installed in the JRE, and their contents are available to all applications running on that JRE and are therefore not private.
Bundled Optional Packages The manifest for an application or optional package can specify one or more relative URLs referring to the JAR files and directories for the optional packages (and other libraries) that it needs. These relative URLs will be treated relative to the code base that the containing application or optional package JAR file was loaded from. An application (or, more generally, JAR file) specifies the relative URLs of the optional packages (and libraries) that it needs via the manifest attribute Class-Path. This attribute lists the URLs to
130
search for implementations of optional packages (or other libraries) if they cannot be found as optional packages installed on the host Java virtual machine*. These relative URLs may include JAR files and directories for any libraries or resources needed by the application or optional package. Relative URLs not ending with '/' are assumed to refer to JAR files. For example, Class-Path: servlet.jar infobus.jar acme/beans.jar images/ At most one Class-Path header may be specified in a JAR file's
manifest..
Currently, the URLs must be relative to the code base of the JAR file for security reasons. Thus, remote optional packages will originate from the same code base as the application. Each relative URL is resolved against the code base that the containing application or optional package was loaded from. If the resulting URL is invalid or refers to a resource that cannot be found then it is ignored. The resulting URLs are used to extend the class path for the application, applet, or servlet by inserting the URLs in the class path immediately following the URL of the containing JAR file. Any duplicate URLs are omitted. For example, given the following class path: a.jar b.jar
If optional package b.jar contained the following Class-Path manifest attribute: Class-Path: x.jar a.jar
Then the resulting application class path would be the following: a.jar b.jar x.jar Of course, if x.jar had dependencies of its own then these would
be added according to the same rules and so on for each subsequent URL. In the actual implementation, JAR file dependencies are processed lazily so that the JAR files are not actually opened until needed.
131
Installed Optional Packages Beginning with Sun's implementation of the Java 2 Platform, the JAR files of a installed optional package are placed in a standard local code source: <java-home>\lib\ext <java-home>/lib/ext
[Microsoft Windows] [Solaris OS, Linux]
Here <java-home> refers to the directory where the runtime software is installed (which is the top-level directory of the JRE or the jre directory in the JDK). The locations for installed optional packages can be specified through the system property java.ext.dirs. This property specifies one or more directories to search for installed optional packages, each separated by File.pathSeparatorChar. The default setting for java.ext.dirs is the standard directory for installed optional packages, as indicated above. For Java 6 and later, the default is enhanced: it is suffixed with the path to a platform-specific directory that is shared by all JREs (Java 6 or later) installed on a system: %SystemRoot%\Sun\Java\lib\ext [Microsoft Windows] /usr/java/packages/lib/ext [Linux] /usr/jdk/packages/lib/ext [Solaris OS]
An installed optional package may also contain one or more shared libraries (such as .dll files) and executables. In what follows, <arch> will be shown but in practice should be the name of an instruction set architecture, for example sparc, sparcv9, i386, and amd64. These can be installed in one of two places. The first to be searched is: <java-home>\bin <java-home>/lib/<arch>
[Microsoft Windows] [Solaris OS, Linux]
The second extension directory to be searched applies only to Java 6 and later. As with Java packages, native libraries can be
132
installed in directories that will be shared by all Java 6 and later JREs: %SystemRoot%\Sun\Java\bin /usr/java/packages/lib/<arch> /usr/jdk/packages/lib/<arch>
[Microsoft Windows] [Linux] [Solaris OS]
An optional package that contains native code cannot be downloaded by network code into the virtual machine at execution time, whether such code is trusted or not. An optional package that contains native code and is bundled with a network application must be installed in the JDK or JRE. By default, installed optional packages in this standard directory are trusted. That is, they are granted the same privileges as if they were core platform classes (those in rt.jar). This default privilege is specified in the system policy file (in <javahome>/jre/lib/security/java.policy), but can be overridden for a particular optional package by adding the appropriate policy file entry (see Permissions in the JDK). Note also that if a installed optional package JAR is signed by a trusted entity, then it will be granted the privileges associated with the trusted signer.
Optional Package Sealing JAR files and packages can be optionally sealed, so that an optional package or package can enforce consistency within a version. A package sealed within a JAR specifies that all classes defined in that package must originate from the same JAR. Otherwise, a SecurityException is thrown. A sealed JAR specifies that all packages defined by that JAR are sealed unless overridden specifically for a package.
133
A sealed package is specified via the manifest attribute, Sealed, whose value is true or false (case irrelevant). For example, Name: javax/servlet/internal/ Sealed: true specifies that the javax.servlet.internal package is sealed, and that
all classes in that package must be loaded from the same JAR file. If this attribute is missing, the package sealing attribute is that of the containing JAR file. A sealed JAR is specified via the same manifest header, Sealed, with the value again of either true or false. For example, Sealed: true
specifies that all packages in this archive are sealed unless explicitly overridden for a particular package with the Sealed attribute in a manifest entry. If this attribute is missing, the JAR file is assumed to not be sealed, for backwards compatibility. The system then defaults to examining package headers for sealing information. Package sealing is also important for security, because it restricts access to package-protected members to only those classes defined in the package that originated from the same JAR file. Package sealing is checked for installed as well as downloaded optional packages, and will result in a SecurityException if violated. Also, the null package is not sealable, so classes that are to be sealed must be placed in their own packages.
Optional Package Security The code source for a installed optional package (namely <javahome>/lib/ext) has a pre-configured security policy associated with it. In Sun's implementation, the exact level of trust granted to
134
JARs in this directory is specified by the standard security policy configuration file <java-home>/lib/security/java.policy
The default policy is for a installed optional package to behave the same way it would if were part of the core platform. This follows from the common need for a installed optional package to load native code. The Java Security Model provides some safety when installed optional package code is called from untrusted code. However optional package code must be carefully reviewed for potential security breaches wherever it uses privileged blocks. A remotely loaded optional package that needs to use accesschecked system services (such as file I/O) to function correctly must either be signed by a trusted entity or loaded from a trusted source. Consult the Java security documentation for further details regarding how to write optional package and application code to use the security features of the Java Platform.
Related APIs Several classes in the Java platform support the extension mechanism, including:
public class java.lang.ClassLoader public class java.lang.Package public class java.net.URLClassLoader
*As used on this web site, the terms "Java Virtual Machine" or "JVM" mean a virtual machine for the Java platform.
Generic Introduction
135
JDK 5.0 introduces several new extensions to the Java programming language. One of these is the introduction of generics. This trail is an introduction to generics. You may be familiar with similar constructs from other languages, most notably C++ templates. If so, you'll see that there are both similarities and important differences. If you are unfamiliar with look-a-alike constructs from elsewhere, all the better; you can start fresh, without having to unlearn any misconceptions. Generics allow you to abstract over types. The most common examples are container types, such as those in the Collections hierarchy. Here is a typical usage of that sort: List myIntList = new LinkedList(); // 1 myIntList.add(new Integer(0)); // 2 Integer x = (Integer) myIntList.iterator().next(); // 3
The cast on line 3 is slightly annoying. Typically, the programmer knows what kind of data has been placed into a particular list. However, the cast is essential. The compiler can only guarantee that an Object will be returned by the iterator. To ensure the assignment to a variable of type Integer is type safe, the cast is required. Of course, the cast not only introduces clutter. It also introduces the possibility of a run time error, since the programmer may be mistaken. What if programmers could actually express their intent, and mark a list as being restricted to contain a particular data type? This is the core idea behind generics. Here is a version of the program fragment given above using generics: List myIntList = new LinkedList(); // 1' myIntList.add(new Integer(0)); // 2'
136
Integer x = myIntList.iterator().next(); // 3'
Notice the type declaration for the variable myIntList. It specifies that this is not just an arbitrary List, but a List of Integer, written List. We say that List is a generic interface that takes a type parameter--in this case, Integer. We also specify a type parameter when creating the list object. Note, too, that the cast on line 3' is gone. Now, you might think that all we've accomplished is to move the clutter around. Instead of a cast to Integer on line 3, we have Integer as a type parameter on line 1'. However, there is a very big difference here. The compiler can now check the type correctness of the program at compile-time. When we say that myIntList is declared with type List, this tells us something about the variable myIntList, which holds true wherever and whenever it is used, and the compiler will guarantee it. In contrast, the cast tells us something the programmer thinks is true at a single point in the code. The net effect, especially in large programs, is improved readability and robustness. Generics and Subtyping Let's test your understanding of generics. Is the following code snippet legal? List<String> ls = new ArrayList<String>(); // 1 List