FREE
2010 Learn Java eBook
eBook
New and Classic Titles for Java Programmers
Buy from InformIT and SAVE 35% Enter the coupon code JAVAONE10 during checkout
SAMPLE CHAPTERS
informit.com/learnjava
eBook Table of Contents
Implementing SOA Using Java EE
Java™ Puzzlers
9780321356680
9780321492159
CHAPTER 3: Methods Common to All Objects
CHAPTER 15: Service Oriented Architecture and the Business Tier
A JAVA PUZZLERS SAMPLE
Java Concurrency in Practice
JavaFX Developer’s Guide
OSGi and Equinox
9780321349606
9780321601650
CHAPTER 11: Performance and Scalability
CHAPTER 27: Using External Data Sources
CHAPTER 3: Tutorial Introduction
Effective Java™, Second Edition
• On Software Podcast Channel LEARN MORE • Safari Books Online LEARN MORE Google Bookmarks
Delicious
Digg
9780321336781
9780321585714
Core JavaServer Faces, Third Edition 9780137012893
CHAPTER 8: Event Handling 304
Growing ObjectOriented Software, Guided by Tests 9780321503626
CHAPTER 2: Test-Driven Development with Objects
• InformIT LEARN MORE • Learn Java™ Web Page LEARN MORE
StumbleUpon
informit.com/learnjava
Many of the designations used by manufacturers and sellers to distinguish their products are claimed as trademarks. Where those designations appear in this book, and Pearson Education was aware of a trademark claim, the designations have been printed with initial capital letters or in all capitals. The authors and publisher have taken care in the preparation of this book, but make no expressed or implied warranty of any kind and assume no responsibility for errors or omissions. No liability is assumed for incidental or consequential damages in connection with or arising out of the use of the information or programs contained herein. Copyright Š 2010 by Pearson Education, Inc.
BROUGHT TO YOU BY ADDISON-WESLEY PROFESSIONAL AND PRENTICE HALL UPPER SADDLE RIVER, NJ | BOSTON | INDIANAPOLIS | SAN FRANCISCO | NEW YORK | TORONTO | MONTREAL LONDON | MUNICH | PARIS | MADRID | CAPETOWN | SYDNEY | TOKYO | SINGAPORE | MEXICO CITY
informit.com/learnjava
CHAPTER 3
Methods Common to All Objects
BUY
from informIT and
SAVE 35%
Enter the coupon code JAVAONE10 during checkout.
Google Bookmarks
Delicious
Digg
StumbleUpon
Effective Java, Second Edition AUTHOR Joshua Bloch
FORMATS & ISBNs Book: 9780321356680 eBook: 9780132345286 Safari Books Online: 9780137150021 “ I sure wish I had this book ten years ago. Some might think that I don’t need any Java books, but I need this one.” — JAMES GOSLING, fellow and vice president, Sun Microsystems, Inc.
“ An excellent book, crammed with good advice on using the Java programming language and object-oriented programming in general.” — GILAD BRACHA, coauthor of The Java™ Language Specification, Third Edition
Effective Java™, Second Edition, presents the most practical, authoritative guidelines available for writing efficient, well-designed programs. Are you looking for a deeper understanding of the Java™ programming language so that you can write code that is clearer, more correct, more robust, and more reusable? Look no further! Effective Java™, Second Edition, brings together seventy-eight indispensable programmer’s rules of thumb: working, bestpractice solutions for the programming challenges you encounter every day.
HIGHLIGHTS INCLUDE: • New coverage of generics, enums, annotations, autoboxing, the for-each loop, varargs, concurrency utilities, and much more • Updated techniques and best practices on classic topics, including objects, classes, libraries, methods, and serialization
• Focus on the language and its most fundamental libraries: java.lang, java.util, and, to a lesser extent, java.util. concurrent and java.io Simply put, Effective Java™, Second Edition, presents the most practical, authoritative guidelines available for writing efficient, well-designed programs.
• How to avoid the traps and pitfalls of commonly misunderstood subtleties of the language
informit.com/learnjava
effective-java.book Page 33 Wednesday, April 23, 2008 4:18 AM
C H A P T E R
3
Methods Common to All Objects ALTHOUGH
is a concrete class, it is designed primarily for extension. All of its nonfinal methods (equals, hashCode, toString, clone, and finalize) have explicit general contracts because they are designed to be overridden. It is the responsibility of any class overriding these methods to obey their general contracts; failure to do so will prevent other classes that depend on the contracts (such as HashMap and HashSet) from functioning properly in conjunction with the class. This chapter tells you when and how to override the nonfinal Object methods. The finalize method is omitted from this chapter because it was discussed in Item 7. While not an Object method, Comparable.compareTo is discussed in this chapter because it has a similar character. Object
Item 8: Obey the general contract when overriding equals Overriding the equals method seems simple, but there are many ways to get it wrong, and consequences can be dire. The easiest way to avoid problems is not to override the equals method, in which case each instance of the class is equal only to itself. This is the right thing to do if any of the following conditions apply: • Each instance of the class is inherently unique. This is true for classes such as Thread that represent active entities rather than values. The equals implementation provided by Object has exactly the right behavior for these classes. • You don’t care whether the class provides a “logical equality” test. For example, java.util.Random could have overridden equals to check whether two Random instances would produce the same sequence of random numbers going forward, but the designers didn’t think that clients would need or want this functionality. Under these circumstances, the equals implementation inherited from Object is adequate. 33
effective-java.book Page 34 Wednesday, April 23, 2008 4:18 AM
34
CHAPTER 3
METHODS COMMON TO ALL OBJECTS
• A superclass has already overridden equals, and the superclass behavior is appropriate for this class. For example, most Set implementations inherit their equals implementation from AbstractSet, List implementations from AbstractList, and Map implementations from AbstractMap. • The class is private or package-private, and you are certain that its equals method will never be invoked. Arguably, the equals method should be overridden under these circumstances, in case it is accidentally invoked: @Override public boolean equals(Object o) { throw new AssertionError(); // Method is never called }
So when is it appropriate to override Object.equals? When a class has a notion of logical equality that differs from mere object identity, and a superclass has not already overridden equals to implement the desired behavior. This is generally the case for value classes. A value class is simply a class that represents a value, such as Integer or Date. A programmer who compares references to value objects using the equals method expects to find out whether they are logically equivalent, not whether they refer to the same object. Not only is overriding the equals method necessary to satisfy programmer expectations; it enables instances to serve as map keys or set elements with predictable, desirable behavior. One kind of value class that does not require the equals method to be overridden is a class that uses instance control (Item 1) to ensure that at most one object exists with each value. Enum types (Item 30) fall into this category. For these classes, logical equality is the same as object identity, so Object’s equals method functions as a logical equals method. When you override the equals method, you must adhere to its general contract. Here is the contract, copied from the specification for Object [JavaSE6]: The equals method implements an equivalence relation. It is: • Reflexive: For any non-null reference value x, x.equals(x) must return true. • Symmetric: For any non-null reference values x and y, x.equals(y) must return true if and only if y.equals(x) returns true. • Transitive: For any non-null reference values x, y, z, if x.equals(y) returns true and y.equals(z) returns true, then x.equals(z) must return true. • Consistent: For any non-null reference values x and y, multiple invocations of x.equals(y) consistently return true or consistently return false, provided no information used in equals comparisons on the objects is modified. • For any non-null reference value x, x.equals(null) must return false.
effective-java.book Page 35 Wednesday, April 23, 2008 4:18 AM
ITEM 8: OBEY THE GENERAL CONTRACT WHEN OVERRIDING EQUALS
Unless you are mathematically inclined, this might look a bit scary, but do not ignore it! If you violate it, you may well find that your program behaves erratically or crashes, and it can be very difficult to pin down the source of the failure. To paraphrase John Donne, no class is an island. Instances of one class are frequently passed to another. Many classes, including all collections classes, depend on the objects passed to them obeying the equals contract. Now that you are aware of the dangers of violating the equals contract, let’s go over the contract in detail. The good news is that, appearances notwithstanding, the contract really isn’t very complicated. Once you understand it, it’s not hard to adhere to it. Let’s examine the five requirements in turn: Reflexivity—The first requirement says merely that an object must be equal to itself. It is hard to imagine violating this requirement unintentionally. If you were to violate it and then add an instance of your class to a collection, the collection’s contains method might well say that the collection didn’t contain the instance that you just added. Symmetry—The second requirement says that any two objects must agree on whether they are equal. Unlike the first requirement, it’s not hard to imagine violating this one unintentionally. For example, consider the following class, which implements a case-insensitive string. The case of the string is preserved by toString but ignored in comparisons: // Broken - violates symmetry! public final class CaseInsensitiveString { private final String s; public CaseInsensitiveString(String s) { if (s == null) throw new NullPointerException(); this.s = s; } // Broken - violates symmetry! @Override public boolean equals(Object o) { if (o instanceof CaseInsensitiveString) return s.equalsIgnoreCase( ((CaseInsensitiveString) o).s); if (o instanceof String) // One-way interoperability! return s.equalsIgnoreCase((String) o); return false; } ... // Remainder omitted }
35
effective-java.book Page 36 Wednesday, April 23, 2008 4:18 AM
36
CHAPTER 3
METHODS COMMON TO ALL OBJECTS
The well-intentioned equals method in this class naively attempts to interoperate with ordinary strings. Let’s suppose that we have one case-insensitive string and one ordinary one: CaseInsensitiveString cis = new CaseInsensitiveString("Polish"); String s = "polish";
As expected, cis.equals(s) returns true. The problem is that while the equals method in CaseInsensitiveString knows about ordinary strings, the equals method in String is oblivious to case-insensitive strings. Therefore s.equals(cis) returns false, a clear violation of symmetry. Suppose you put a case-insensitive string into a collection: List<CaseInsensitiveString> list = new ArrayList<CaseInsensitiveString>(); list.add(cis);
What does list.contains(s) return at this point? Who knows? In Sun’s current implementation, it happens to return false, but that’s just an implementation artifact. In another implementation, it could just as easily return true or throw a runtime exception. Once you’ve violated the equals contract, you simply don’t know how other objects will behave when confronted with your object. To eliminate the problem, merely remove the ill-conceived attempt to interoperate with String from the equals method. Once you do this, you can refactor the method to give it a single return: @Override public boolean equals(Object o) { return o instanceof CaseInsensitiveString && ((CaseInsensitiveString) o).s.equalsIgnoreCase(s); }
Transitivity—The third requirement of the equals contract says that if one object is equal to a second and the second object is equal to a third, then the first object must be equal to the third. Again, it’s not hard to imagine violating this requirement unintentionally. Consider the case of a subclass that adds a new value component to its superclass. In other words, the subclass adds a piece of informa-
effective-java.book Page 37 Wednesday, April 23, 2008 4:18 AM
ITEM 8: OBEY THE GENERAL CONTRACT WHEN OVERRIDING EQUALS
tion that affects equals comparisons. Letâ&#x20AC;&#x2122;s start with a simple immutable twodimensional integer point class: public class Point { private final int x; private final int y; public Point(int x, int y) { this.x = x; this.y = y; } @Override public boolean equals(Object o) { if (!(o instanceof Point)) return false; Point p = (Point)o; return p.x == x && p.y == y; } ...
// Remainder omitted
}
Suppose you want to extend this class, adding the notion of color to a point: public class ColorPoint extends Point { private final Color color; public ColorPoint(int x, int y, Color color) { super(x, y); this.color = color; } ...
// Remainder omitted
}
How should the equals method look? If you leave it out entirely, the implementation is inherited from Point and color information is ignored in equals comparisons. While this does not violate the equals contract, it is clearly unacceptable. Suppose you write an equals method that returns true only if its argument is another color point with the same position and color: // Broken - violates symmetry! @Override public boolean equals(Object o) { if (!(o instanceof ColorPoint)) return false; return super.equals(o) && ((ColorPoint) o).color == color; }
37
effective-java.book Page 38 Friday, May 23, 2008 1:27 PM
38
CHAPTER 3
METHODS COMMON TO ALL OBJECTS
The problem with this method is that you might get different results when comparing a point to a color point and vice versa. The former comparison ignores color, while the latter comparison always returns false because the type of the argument is incorrect. To make this concrete, let’s create one point and one color point: Point p = new Point(1, 2); ColorPoint cp = new ColorPoint(1, 2, Color.RED);
Then p.equals(cp) returns true, while cp.equals(p) returns false. You might try to fix the problem by having ColorPoint.equals ignore color when doing “mixed comparisons”: // Broken - violates transitivity! @Override public boolean equals(Object o) { if (!(o instanceof Point)) return false; // If o is a normal Point, do a color-blind comparison if (!(o instanceof ColorPoint)) return o.equals(this); // o is a ColorPoint; do a full comparison return super.equals(o) && ((ColorPoint) o).color == color; }
This approach does provide symmetry, but at the expense of transitivity: ColorPoint p1 = new ColorPoint(1, 2, Color.RED); Point p2 = new Point(1, 2); ColorPoint p3 = new ColorPoint(1, 2, Color.BLUE);
Now p1.equals(p2) and p2.equals(p3) return true, while p1.equals(p3) returns false, a clear violation of transitivity. The first two comparisons are “color-blind,” while the third takes color into account. So what’s the solution? It turns out that this is a fundamental problem of equivalence relations in object-oriented languages. There is no way to extend an instantiable class and add a value component while preserving the equals contract, unless you are willing to forgo the benefits of object-oriented abstraction.
effective-java.book Page 39 Wednesday, April 23, 2008 4:18 AM
ITEM 8: OBEY THE GENERAL CONTRACT WHEN OVERRIDING EQUALS
You may hear it said that you can extend an instantiable class and add a value component while preserving the equals contract by using a getClass test in place of the instanceof test in the equals method: // Broken - violates Liskov substitution principle (page 40) @Override public boolean equals(Object o) { if (o == null || o.getClass() != getClass()) return false; Point p = (Point) o; return p.x == x && p.y == y; }
This has the effect of equating objects only if they have the same implementation class. While this may not seem so bad, the consequences are unacceptable. Letâ&#x20AC;&#x2122;s suppose we want to write a method to tell whether an integer point is on the unit circle. Here is one way we could do it: // Initialize UnitCircle to contain all Points on the unit circle private static final Set<Point> unitCircle; static { unitCircle = new HashSet<Point>(); unitCircle.add(new Point( 1, 0)); unitCircle.add(new Point( 0, 1)); unitCircle.add(new Point(-1, 0)); unitCircle.add(new Point( 0, -1)); } public static boolean onUnitCircle(Point p) { return unitCircle.contains(p); }
While this may not be the fastest way to implement the functionality, it works fine. But suppose you extend Point in some trivial way that doesnâ&#x20AC;&#x2122;t add a value component, say, by having its constructor keep track of how many instances have been created: public class CounterPoint extends Point { private static final AtomicInteger counter = new AtomicInteger(); public CounterPoint(int x, int y) { super(x, y); counter.incrementAndGet(); } public int numberCreated() { return counter.get(); } }
39
effective-java.book Page 40 Wednesday, April 23, 2008 4:18 AM
40
CHAPTER 3
METHODS COMMON TO ALL OBJECTS
The Liskov substitution principle says that any important property of a type should also hold for its subtypes, so that any method written for the type should work equally well on its subtypes [Liskov87]. But suppose we pass a CounterPoint instance to the onUnitCircle method. If the Point class uses a getClassbased equals method, the onUnitCircle method will return false regardless of the CounterPoint instance’s x and y values. This is so because collections, such as the HashSet used by the onUnitCircle method, use the equals method to test for containment, and no CounterPoint instance is equal to any Point. If, however, you use a proper instanceof-based equals method on Point, the same onUnitCircle method will work fine when presented with a CounterPoint. While there is no satisfactory way to extend an instantiable class and add a value component, there is a fine workaround. Follow the advice of Item 16, “Favor composition over inheritance.” Instead of having ColorPoint extend Point, give ColorPoint a private Point field and a public view method (Item 5) that returns the point at the same position as this color point: // Adds a value component without violating the equals contract public class ColorPoint { private final Point point; private final Color color; public ColorPoint(int x, int y, Color color) { if (color == null) throw new NullPointerException(); point = new Point(x, y); this.color = color; } /** * Returns the point-view of this color point. */ public Point asPoint() { return point; } @Override public boolean equals(Object o) { if (!(o instanceof ColorPoint)) return false; ColorPoint cp = (ColorPoint) o; return cp.point.equals(point) && cp.color.equals(color); } ... }
// Remainder omitted
effective-java.book Page 41 Wednesday, April 23, 2008 4:18 AM
ITEM 8: OBEY THE GENERAL CONTRACT WHEN OVERRIDING EQUALS
There are some classes in the Java platform libraries that do extend an instantiable class and add a value component. For example, java.sql.Timestamp extends java.util.Date and adds a nanoseconds field. The equals implementation for Timestamp does violate symmetry and can cause erratic behavior if Timestamp and Date objects are used in the same collection or are otherwise intermixed. The Timestamp class has a disclaimer cautioning programmers against mixing dates and timestamps. While you won’t get into trouble as long as you keep them separate, there’s nothing to prevent you from mixing them, and the resulting errors can be hard to debug. This behavior of the Timestamp class was a mistake and should not be emulated. Note that you can add a value component to a subclass of an abstract class without violating the equals contract. This is important for the sort of class hierarchies that you get by following the advice in Item 20, “Prefer class hierarchies to tagged classes.” For example, you could have an abstract class Shape with no value components, a subclass Circle that adds a radius field, and a subclass Rectangle that adds length and width fields. Problems of the sort shown above won’t occur so long as it is impossible to create a superclass instance directly. Consistency—The fourth requirement of the equals contract says that if two objects are equal, they must remain equal for all time unless one (or both) of them is modified. In other words, mutable objects can be equal to different objects at different times while immutable objects can’t. When you write a class, think hard about whether it should be immutable (Item 15). If you conclude that it should, make sure that your equals method enforces the restriction that equal objects remain equal and unequal objects remain unequal for all time. Whether or not a class is immutable, do not write an equals method that depends on unreliable resources. It’s extremely difficult to satisfy the consistency requirement if you violate this prohibition. For example, java.net.URL’s equals method relies on comparison of the IP addresses of the hosts associated with the URLs. Translating a host name to an IP address can require network access, and it isn’t guaranteed to yield the same results over time. This can cause the URL equals method to violate the equals contract and has caused problems in practice. (Unfortunately, this behavior cannot be changed due to compatibility requirements.) With very few exceptions, equals methods should perform deterministic computations on memory-resident objects. “Non-nullity”—The final requirement, which in the absence of a name I have taken the liberty of calling “non-nullity,” says that all objects must be unequal to null. While it is hard to imagine accidentally returning true in response to the invocation o.equals(null), it isn’t hard to imagine accidentally throwing a
41
effective-java.book Page 42 Wednesday, April 23, 2008 4:18 AM
42
CHAPTER 3
METHODS COMMON TO ALL OBJECTS
NullPointerException.
The general contract does not allow this. Many classes have equals methods that guard against this with an explicit test for null: @Override public boolean equals(Object o) { if (o == null) return false; ... }
This test is unnecessary. To test its argument for equality, the equals method must first cast its argument to an appropriate type so its accessors may be invoked or its fields accessed. Before doing the cast, the method must use the instanceof operator to check that its argument is of the correct type: @Override public boolean equals(Object o) { if (!(o instanceof MyType)) return false; MyType mt = (MyType) o; ... }
If this type check were missing and the equals method were passed an argument of the wrong type, the equals method would throw a ClassCastException, which violates the equals contract. But the instanceof operator is specified to return false if its first operand is null, regardless of what type appears in the second operand [JLS, 15.20.2]. Therefore the type check will return false if null is passed in, so you donâ&#x20AC;&#x2122;t need a separate null check. Putting it all together, hereâ&#x20AC;&#x2122;s a recipe for a high-quality equals method: 1. Use the == operator to check if the argument is a reference to this object. If so, return true. This is just a performance optimization, but one that is worth doing if the comparison is potentially expensive. 2. Use the instanceof operator to check if the argument has the correct type. If not, return false. Typically, the correct type is the class in which the method occurs. Occasionally, it is some interface implemented by this class. Use an interface if the class implements an interface that refines the equals contract to permit comparisons across classes that implement the interface. Collection interfaces such as Set, List, Map, and Map.Entry have this property. 3. Cast the argument to the correct type. Because this cast was preceded by an instanceof test, it is guaranteed to succeed.
effective-java.book Page 43 Wednesday, April 23, 2008 4:18 AM
ITEM 8: OBEY THE GENERAL CONTRACT WHEN OVERRIDING EQUALS
4. For each “significant” field in the class, check if that field of the argument matches the corresponding field of this object. If all these tests succeed, return true; otherwise, return false. If the type in step 2 is an interface, you must access the argument’s fields via interface methods; if the type is a class, you may be able to access the fields directly, depending on their accessibility. For primitive fields whose type is not float or double, use the == operator for comparisons; for object reference fields, invoke the equals method recursively; for float fields, use the Float.compare method; and for double fields, use Double.compare. The special treatment of float and double fields is made necessary by the existence of Float.NaN, -0.0f and the analogous double constants; see the Float.equals documentation for details. For array fields, apply these guidelines to each element. If every element in an array field is significant, you can use one of the Arrays.equals methods added in release 1.5. Some object reference fields may legitimately contain null. To avoid the possibility of a NullPointerException, use this idiom to compare such fields: (field == null ? o.field == null : field.equals(o.field))
This alternative may be faster if field and o.field are often identical: (field == o.field || (field != null && field.equals(o.field)))
For some classes, such as CaseInsensitiveString above, field comparisons are more complex than simple equality tests. If this is the case, you may want to store a canonical form of the field, so the equals method can do cheap exact comparisons on these canonical forms rather than more costly inexact comparisons. This technique is most appropriate for immutable classes (Item 15); if the object can change, you must keep the canonical form up to date. The performance of the equals method may be affected by the order in which fields are compared. For best performance, you should first compare fields that are more likely to differ, less expensive to compare, or, ideally, both. You must not compare fields that are not part of an object’s logical state, such as Lock fields used to synchronize operations. You need not compare redundant fields, which can be calculated from “significant fields,” but doing so may improve the performance of the equals method. If a redundant field amounts to a summary description of the entire object, comparing this field will save you the expense of comparing the actual data if the comparison fails. For example, suppose you have a Polygon class, and you cache the area. If two polygons have unequal areas, you needn’t bother comparing their edges and vertices.
43
effective-java.book Page 44 Wednesday, April 23, 2008 4:18 AM
44
CHAPTER 3
METHODS COMMON TO ALL OBJECTS
5. When you are finished writing your equals method, ask yourself three questions: Is it symmetric? Is it transitive? Is it consistent? And don’t just ask yourself; write unit tests to check that these properties hold! If they don’t, figure out why not, and modify the equals method accordingly. Of course your equals method also has to satisfy the other two properties (reflexivity and “non-nullity”), but these two usually take care of themselves. For a concrete example of an equals method constructed according to the above recipe, see PhoneNumber.equals in Item 9. Here are a few final caveats: • Always override hashCode when you override equals (Item 9). • Don’t try to be too clever. If you simply test fields for equality, it’s not hard to adhere to the equals contract. If you are overly aggressive in searching for equivalence, it’s easy to get into trouble. It is generally a bad idea to take any form of aliasing into account. For example, the File class shouldn’t attempt to equate symbolic links referring to the same file. Thankfully, it doesn’t. • Don’t substitute another type for Object in the equals declaration. It is not uncommon for a programmer to write an equals method that looks like this, and then spend hours puzzling over why it doesn’t work properly: public boolean equals(MyClass o) { ... }
The problem is that this method does not override Object.equals, whose argument is of type Object, but overloads it instead (Item 41). It is acceptable to provide such a “strongly typed” equals method in addition to the normal one as long as the two methods return the same result, but there is no compelling reason to do so. It may provide minor performance gains under certain circumstances, but it isn’t worth the added complexity (Item 55). Consistent use of the @Override annotation, as illustrated throughout this item, will prevent you from making this mistake (Item 36). This equals method won’t compile and the error message will tell you exactly what is wrong: @Override public boolean equals(MyClass o) { ... }
effective-java.book Page 45 Wednesday, April 23, 2008 4:18 AM
ITEM 9: ALWAYS OVERRIDE HASHCODE WHEN YOU OVERRIDE EQUALS
Item 9:
Always override hashCode when you override equals
A common source of bugs is the failure to override the hashCode method. You must override hashCode in every class that overrides equals. Failure to do so will result in a violation of the general contract for Object.hashCode, which will prevent your class from functioning properly in conjunction with all hash-based collections, including HashMap, HashSet, and Hashtable. Here is the contract, copied from the Object specification [JavaSE6]: • Whenever it is invoked on the same object more than once during an execution of an application, the hashCode method must consistently return the same integer, provided no information used in equals comparisons on the object is modified. This integer need not remain consistent from one execution of an application to another execution of the same application. • If two objects are equal according to the equals(Object) method, then calling the hashCode method on each of the two objects must produce the same integer result. • It is not required that if two objects are unequal according to the equals(Object) method, then calling the hashCode method on each of the two objects must produce distinct integer results. However, the programmer should be aware that producing distinct integer results for unequal objects may improve the performance of hash tables. The key provision that is violated when you fail to override hashCode is the second one: equal objects must have equal hash codes. Two distinct instances may be logically equal according to a class’s equals method, but to Object’s hashCode method, they’re just two objects with nothing much in common. Therefore Object’s hashCode method returns two seemingly random numbers instead of two equal numbers as required by the contract. For example, consider the following simplistic PhoneNumber class, whose equals method is constructed according to the recipe in Item 8: public final class PhoneNumber { private final short areaCode; private final short prefix; private final short lineNumber; public PhoneNumber(int areaCode, int prefix, int lineNumber) { rangeCheck(areaCode, 999, "area code"); rangeCheck(prefix, 999, "prefix"); rangeCheck(lineNumber, 9999, "line number");
45
effective-java.book Page 46 Wednesday, April 23, 2008 4:18 AM
46
CHAPTER 3
METHODS COMMON TO ALL OBJECTS
this.areaCode = (short) areaCode; this.prefix = (short) prefix; this.lineNumber = (short) lineNumber; } private static void rangeCheck(int arg, int max, String name) { if (arg < 0 || arg > max) throw new IllegalArgumentException(name +": " + arg); } @Override public boolean equals(Object o) { if (o == this) return true; if (!(o instanceof PhoneNumber)) return false; PhoneNumber pn = (PhoneNumber)o; return pn.lineNumber == lineNumber && pn.prefix == prefix && pn.areaCode == areaCode; } // Broken - no hashCode method! ... // Remainder omitted }
Suppose you attempt to use this class with a HashMap: Map<PhoneNumber, String> m = new HashMap<PhoneNumber, String>(); m.put(new PhoneNumber(707, 867, 5309), "Jenny");
At this point, you might expect m.get(new PhoneNumber(707, 867, 5309)) to return "Jenny", but it returns null. Notice that two PhoneNumber instances are involved: one is used for insertion into the HashMap, and a second, equal, instance is used for (attempted) retrieval. The PhoneNumber class’s failure to override hashCode causes the two equal instances to have unequal hash codes, in violation of the hashCode contract. Therefore the get method is likely to look for the phone number in a different hash bucket from the one in which it was stored by the put method. Even if the two instances happen to hash to the same bucket, the get method will almost certainly return null, as HashMap has an optimization that caches the hash code associated with each entry and doesn’t bother checking for object equality if the hash codes don’t match.
effective-java.book Page 47 Wednesday, April 23, 2008 4:18 AM
ITEM 9: ALWAYS OVERRIDE HASHCODE WHEN YOU OVERRIDE EQUALS
Fixing this problem is as simple as providing a proper hashCode method for the PhoneNumber class. So what should a hashCode method look like? It’s trivial to write one that is legal but not good. This one, for example, is always legal but should never be used: // The worst possible legal hash function - never use! @Override public int hashCode() { return 42; }
It’s legal because it ensures that equal objects have the same hash code. It’s atrocious because it ensures that every object has the same hash code. Therefore, every object hashes to the same bucket, and hash tables degenerate to linked lists. Programs that should run in linear time instead run in quadratic time. For large hash tables, this is the difference between working and not working. A good hash function tends to produce unequal hash codes for unequal objects. This is exactly what is meant by the third provision of the hashCode contract. Ideally, a hash function should distribute any reasonable collection of unequal instances uniformly across all possible hash values. Achieving this ideal can be difficult. Luckily it’s not too difficult to achieve a fair approximation. Here is a simple recipe: 1. Store some constant nonzero value, say, 17, in an int variable called result. 2. For each significant field f in your object (each field taken into account by the equals method, that is), do the following: a. Compute an int hash code c for the field: i. If the field is a boolean, compute (f ? 1 : 0). ii. If the field is a byte, char, short, or int, compute (int) f. iii. If the field is a long, compute (int) (f ^ (f >>> 32)). iv. If the field is a float, compute Float.floatToIntBits(f). v. If the field is a double, compute Double.doubleToLongBits(f), and then hash the resulting long as in step 2.a.iii. vi. If the field is an object reference and this class’s equals method compares the field by recursively invoking equals, recursively invoke hashCode on the field. If a more complex comparison is required, compute a “canonical representation” for this field and invoke hashCode on the canonical representation. If the value of the field is null, return 0 (or some other constant, but 0 is traditional).
47
effective-java.book Page 48 Wednesday, April 23, 2008 4:18 AM
48
CHAPTER 3
METHODS COMMON TO ALL OBJECTS
vii. If the field is an array, treat it as if each element were a separate field. That is, compute a hash code for each significant element by applying these rules recursively, and combine these values per step 2.b. If every element in an array field is significant, you can use one of the Arrays.hashCode methods added in release 1.5. b. Combine the hash code c computed in step 2.a into result as follows: result = 31 * result + c;
3. Return result. 4. When you are finished writing the hashCode method, ask yourself whether equal instances have equal hash codes. Write unit tests to verify your intuition! If equal instances have unequal hash codes, figure out why and fix the problem. You may exclude redundant fields from the hash code computation. In other words, you may ignore any field whose value can be computed from fields included in the computation. You must exclude any fields that are not used in equals comparisons, or you risk violating the second provision of the hashCode contract. A nonzero initial value is used in step 1 so the hash value will be affected by initial fields whose hash value, as computed in step 2.a, is zero. If zero were used as the initial value in step 1, the overall hash value would be unaffected by any such initial fields, which could increase collisions. The value 17 is arbitrary. The multiplication in step 2.b makes the result depend on the order of the fields, yielding a much better hash function if the class has multiple similar fields. For example, if the multiplication were omitted from a String hash function, all anagrams would have identical hash codes. The value 31 was chosen because it is an odd prime. If it were even and the multiplication overflowed, information would be lost, as multiplication by 2 is equivalent to shifting. The advantage of using a prime is less clear, but it is traditional. A nice property of 31 is that the multiplication can be replaced by a shift and a subtraction for better performance: 31 * i == (i << 5) - i. Modern VMs do this sort of optimization automatically. Letâ&#x20AC;&#x2122;s apply the above recipe to the PhoneNumber class. There are three significant fields, all of type short: @Override public int hashCode() { int result = 17; result = 31 * result + areaCode; result = 31 * result + prefix; result = 31 * result + lineNumber; return result; }
effective-java.book Page 49 Wednesday, April 23, 2008 4:18 AM
ITEM 9: ALWAYS OVERRIDE HASHCODE WHEN YOU OVERRIDE EQUALS
Because this method returns the result of a simple deterministic computation whose only inputs are the three significant fields in a PhoneNumber instance, it is clear that equal PhoneNumber instances have equal hash codes. This method is, in fact, a perfectly good hashCode implementation for PhoneNumber, on a par with those in the Java platform libraries. It is simple, reasonably fast, and does a reasonable job of dispersing unequal phone numbers into different hash buckets. If a class is immutable and the cost of computing the hash code is significant, you might consider caching the hash code in the object rather than recalculating it each time it is requested. If you believe that most objects of this type will be used as hash keys, then you should calculate the hash code when the instance is created. Otherwise, you might choose to lazily initialize it the first time hashCode is invoked (Item 71). It is not clear that our PhoneNumber class merits this treatment, but just to show you how itâ&#x20AC;&#x2122;s done: // Lazily initialized, cached hashCode private volatile int hashCode; // (See Item 71) @Override public int hashCode() { int result = hashCode; if (result == 0) { result = 17; result = 31 * result + areaCode; result = 31 * result + prefix; result = 31 * result + lineNumber; hashCode = result; } return result; }
While the recipe in this item yields reasonably good hash functions, it does not yield state-of-the-art hash functions, nor do the Java platform libraries provide such hash functions as of release 1.6. Writing such hash functions is a research topic, best left to mathematicians and theoretical computer scientists. Perhaps a later release of the platform will provide state-of-the-art hash functions for its classes and utility methods to allow average programmers to construct such hash functions. In the meantime, the techniques described in this item should be adequate for most applications. Do not be tempted to exclude significant parts of an object from the hash code computation to improve performance. While the resulting hash function may run faster, its poor quality may degrade hash tablesâ&#x20AC;&#x2122; performance to the point where they become unusably slow. In particular, the hash function may, in prac-
49
effective-java.book Page 50 Wednesday, April 23, 2008 4:18 AM
50
CHAPTER 3
METHODS COMMON TO ALL OBJECTS
tice, be confronted with a large collection of instances that differ largely in the regions that youâ&#x20AC;&#x2122;ve chosen to ignore. If this happens, the hash function will map all the instances to a very few hash codes, and hash-based collections will display quadratic performance. This is not just a theoretical problem. The String hash function implemented in all releases prior to 1.2 examined at most sixteen characters, evenly spaced throughout the string, starting with the first character. For large collections of hierarchical names, such as URLs, this hash function displayed exactly the pathological behavior noted here. Many classes in the Java platform libraries, such as String, Integer, and Date, include in their specifications the exact value returned by their hashCode method as a function of the instance value. This is generally not a good idea, as it severely limits your ability to improve the hash function in future releases. If you leave the details of a hash function unspecified and a flaw is found or a better hash function discovered, you can change the hash function in a subsequent release, confident that no clients depend on the exact values returned by the hash function.
effective-java.book Page 51 Wednesday, April 23, 2008 4:18 AM
ITEM 10: ALWAYS OVERRIDE TOSTRING
Item 10: Always override toString While java.lang.Object provides an implementation of the toString method, the string that it returns is generally not what the user of your class wants to see. It consists of the class name followed by an “at” sign (@) and the unsigned hexadecimal representation of the hash code, for example, “PhoneNumber@163b91.” The general contract for toString says that the returned string should be “a concise but informative representation that is easy for a person to read” [JavaSE6]. While it could be argued that “PhoneNumber@163b91” is concise and easy to read, it isn’t very informative when compared to “(707) 867-5309.” The toString contract goes on to say, “It is recommended that all subclasses override this method.” Good advice, indeed! While it isn’t as important as obeying the equals and hashCode contracts (Item 8, Item 9), providing a good toString implementation makes your class much more pleasant to use. The toString method is automatically invoked when an object is passed to println, printf, the string concatenation operator, or assert, or printed by a debugger. (The printf method was added to the platform in release 1.5, as were related methods including String.format, which is roughly equivalent to C’s sprintf.) If you’ve provided a good toString method for PhoneNumber, generating a useful diagnostic message is as easy as this: System.out.println("Failed to connect: " + phoneNumber);
Programmers will generate diagnostic messages in this fashion whether or not you override toString, but the messages won’t be useful unless you do. The benefits of providing a good toString method extend beyond instances of the class to objects containing references to these instances, especially collections. Which would you rather see when printing a map, “{Jenny=PhoneNumber@163b91}” or “{Jenny=(707) 867-5309}”? When practical, the toString method should return all of the interesting information contained in the object, as in the phone number example just shown. It is impractical if the object is large or if it contains state that is not conducive to string representation. Under these circumstances, toString should return a summary such as “Manhattan white pages (1487536 listings)” or “Thread[main,5,main]”. Ideally, the string should be self-explanatory. (The Thread example flunks this test.)
51
effective-java.book Page 52 Wednesday, April 23, 2008 4:18 AM
52
CHAPTER 3
METHODS COMMON TO ALL OBJECTS
One important decision you’ll have to make when implementing a toString method is whether to specify the format of the return value in the documentation. It is recommended that you do this for value classes, such as phone numbers or matrices. The advantage of specifying the format is that it serves as a standard, unambiguous, human-readable representation of the object. This representation can be used for input and output and in persistent human-readable data objects, such as XML documents. If you specify the format, it’s usually a good idea to provide a matching static factory or constructor so programmers can easily translate back and forth between the object and its string representation. This approach is taken by many value classes in the Java platform libraries, including BigInteger, BigDecimal, and most of the boxed primitive classes. The disadvantage of specifying the format of the toString return value is that once you’ve specified it, you’re stuck with it for life, assuming your class is widely used. Programmers will write code to parse the representation, to generate it, and to embed it into persistent data. If you change the representation in a future release, you’ll break their code and data, and they will yowl. By failing to specify a format, you preserve the flexibility to add information or improve the format in a subsequent release. Whether or not you decide to specify the format, you should clearly document your intentions. If you specify the format, you should do so precisely. For example, here’s a toString method to go with the PhoneNumber class in Item 9: /** * Returns the string representation of this phone number. * The string consists of fourteen characters whose format * is "(XXX) YYY-ZZZZ", where XXX is the area code, YYY is * the prefix, and ZZZZ is the line number. (Each of the * capital letters represents a single decimal digit.) * * If any of the three parts of this phone number is too small * to fill up its field, the field is padded with leading zeros. * For example, if the value of the line number is 123, the last * four characters of the string representation will be "0123". * * Note that there is a single space separating the closing * parenthesis after the area code from the first digit of the * prefix. */ @Override public String toString() { return String.format("(%03d) %03d-%04d", areaCode, prefix, lineNumber); }
effective-java.book Page 53 Wednesday, April 23, 2008 4:18 AM
ITEM 10: ALWAYS OVERRIDE TOSTRING
If you decide not to specify a format, the documentation comment should read something like this: /** * Returns a brief description of this potion. The exact details * of the representation are unspecified and subject to change, * but the following may be regarded as typical: * * "[Potion #9: type=love, smell=turpentine, look=india ink]" */ @Override public String toString() { ... }
After reading this comment, programmers who produce code or persistent data that depends on the details of the format will have no one but themselves to blame when the format is changed. Whether or not you specify the format, provide programmatic access to all of the information contained in the value returned by toString. For example, the PhoneNumber class should contain accessors for the area code, prefix, and line number. If you fail to do this, you force programmers who need this information to parse the string. Besides reducing performance and making unnecessary work for programmers, this process is error-prone and results in fragile systems that break if you change the format. By failing to provide accessors, you turn the string format into a de facto API, even if youâ&#x20AC;&#x2122;ve specified that itâ&#x20AC;&#x2122;s subject to change.
53
effective-java.book Page 54 Wednesday, April 23, 2008 4:18 AM
54
CHAPTER 3
METHODS COMMON TO ALL OBJECTS
Item 11: Override clone judiciously The Cloneable interface was intended as a mixin interface (Item 18) for objects to advertise that they permit cloning. Unfortunately, it fails to serve this purpose. Its primary flaw is that it lacks a clone method, and Object’s clone method is protected. You cannot, without resorting to reflection (Item 53), invoke the clone method on an object merely because it implements Cloneable. Even a reflective invocation may fail, as there is no guarantee that the object has an accessible clone method. Despite this flaw and others, the facility is in wide use so it pays to understand it. This item tells you how to implement a well-behaved clone method, discusses when it is appropriate to do so, and presents alternatives. So what does Cloneable do, given that it contains no methods? It determines the behavior of Object’s protected clone implementation: if a class implements Cloneable, Object’s clone method returns a field-by-field copy of the object; otherwise it throws CloneNotSupportedException. This is a highly atypical use of interfaces and not one to be emulated. Normally, implementing an interface says something about what a class can do for its clients. In the case of Cloneable, it modifies the behavior of a protected method on a superclass. If implementing the Cloneable interface is to have any effect on a class, the class and all of its superclasses must obey a fairly complex, unenforceable, and thinly documented protocol. The resulting mechanism is extralinguistic: it creates an object without calling a constructor. The general contract for the clone method is weak. Here it is, copied from the specification for java.lang.Object [JavaSE6]: Creates and returns a copy of this object. The precise meaning of “copy” may depend on the class of the object. The general intent is that, for any object x, the expression x.clone() != x
will be true, and the expression x.clone().getClass() == x.getClass()
will be true, but these are not absolute requirements. While it is typically the case that x.clone().equals(x)
will be true, this is not an absolute requirement. Copying an object will typically entail creating a new instance of its class, but it may require copying of internal data structures as well. No constructors are called.
effective-java.book Page 55 Wednesday, April 23, 2008 4:18 AM
ITEM 11: OVERRIDE CLONE JUDICIOUSLY
There are a number of problems with this contract. The provision that “no constructors are called” is too strong. A well-behaved clone method can call constructors to create objects internal to the clone under construction. If the class is final, clone can even return an object created by a constructor. The provision that x.clone().getClass() should generally be identical to x.getClass(), however, is too weak. In practice, programmers assume that if they extend a class and invoke super.clone from the subclass, the returned object will be an instance of the subclass. The only way a superclass can provide this functionality is to return an object obtained by calling super.clone. If a clone method returns an object created by a constructor, it will have the wrong class. Therefore, if you override the clone method in a nonfinal class, you should return an object obtained by invoking super.clone. If all of a class’s superclasses obey this rule, then invoking super.clone will eventually invoke Object’s clone method, creating an instance of the right class. This mechanism is vaguely similar to automatic constructor chaining, except that it isn’t enforced. The Cloneable interface does not, as of release 1.6, spell out in detail the responsibilities that a class takes on when it implements this interface. In practice, a class that implements Cloneable is expected to provide a properly functioning public clone method. It is not, in general, possible to do so unless all of the class’s superclasses provide a well-behaved clone implementation, whether public or protected. Suppose you want to implement Cloneable in a class whose superclasses provide well-behaved clone methods. The object you get from super.clone() may or may not be close to what you’ll eventually return, depending on the nature of the class. This object will be, from the standpoint of each superclass, a fully functional clone of the original object. The fields declared in your class (if any) will have values identical to those of the object being cloned. If every field contains a primitive value or a reference to an immutable object, the returned object may be exactly what you need, in which case no further processing is necessary. This is the case, for example, for the PhoneNumber class in Item 9. In this case, all you need do in addition to declaring that you implement Cloneable is to provide public access to Object’s protected clone method: @Override public PhoneNumber clone() { try { return (PhoneNumber) super.clone(); } catch(CloneNotSupportedException e) { throw new AssertionError(); // Can't happen } }
55
effective-java.book Page 56 Wednesday, April 23, 2008 4:18 AM
56
CHAPTER 3
METHODS COMMON TO ALL OBJECTS
Note that the above clone method returns PhoneNumber, not Object. As of release 1.5, it is legal and desirable to do this, because covariant return types were introduced in release 1.5 as part of generics. In other words, it is now legal for an overriding methodâ&#x20AC;&#x2122;s return type to be a subclass of the overridden methodâ&#x20AC;&#x2122;s return type. This allows the overriding method to provide more information about the returned object and eliminates the need for casting in the client. Because Object.clone returns Object, PhoneNumber.clone must cast the result of super.clone() before returning it, but this is far preferable to requiring every caller of PhoneNumber.clone to cast the result. The general principle at play here is never make the client do anything the library can do for the client. If an object contains fields that refer to mutable objects, using the simple clone implementation shown above can be disastrous. For example, consider the Stack class in Item 6: public class Stack { private Object[] elements; private int size = 0; private static final int DEFAULT_INITIAL_CAPACITY = 16; public Stack() { this.elements = new Object[DEFAULT_INITIAL_CAPACITY]; } public void push(Object e) { ensureCapacity(); elements[size++] = e; } public Object pop() { if (size == 0) throw new EmptyStackException(); Object result = elements[--size]; elements[size] = null; // Eliminate obsolete reference return result; } // Ensure space for at least one more element. private void ensureCapacity() { if (elements.length == size) elements = Arrays.copyOf(elements, 2 * size + 1); } }
Suppose you want to make this class cloneable. If its clone method merely returns super.clone(), the resulting Stack instance will have the correct value in
effective-java.book Page 57 Wednesday, April 23, 2008 4:18 AM
ITEM 11: OVERRIDE CLONE JUDICIOUSLY
its size field, but its elements field will refer to the same array as the original Stack instance. Modifying the original will destroy the invariants in the clone and vice versa. You will quickly find that your program produces nonsensical results or throws a NullPointerException. This situation could never occur as a result of calling the sole constructor in the Stack class. In effect, the clone method functions as another constructor; you must ensure that it does no harm to the original object and that it properly establishes invariants on the clone. In order for the clone method on Stack to work properly, it must copy the internals of the stack. The easiest way to do this is to call clone recursively on the elements array: @Override public Stack clone() { try { Stack result = (Stack) super.clone(); result.elements = elements.clone(); return result; } catch (CloneNotSupportedException e) { throw new AssertionError(); } }
Note that we do not have to cast the result of elements.clone() to Object[]. As of release 1.5, calling clone on an array returns an array whose compile-time type is the same as that of the array being cloned. Note also that the above solution would not work if the elements field were final, because clone would be prohibited from assigning a new value to the field. This is a fundamental problem: the clone architecture is incompatible with normal use of final fields referring to mutable objects, except in cases where the mutable objects may be safely shared between an object and its clone. In order to make a class cloneable, it may be necessary to remove final modifiers from some fields. It is not always sufficient to call clone recursively. For example, suppose you are writing a clone method for a hash table whose internals consist of an array of buckets, each of which references the first entry in a linked list of key-value pairs or is null if the bucket is empty. For performance, the class implements its own lightweight singly linked list instead of using java.util.LinkedList internally: public class HashTable implements Cloneable { private Entry[] buckets = ...;
57
effective-java.book Page 58 Wednesday, April 23, 2008 4:18 AM
58
CHAPTER 3
METHODS COMMON TO ALL OBJECTS
private static class Entry { final Object key; Object value; Entry next; Entry(Object key, Object value, Entry next) { this.key = key; this.value = value; this.next = next; } } ... // Remainder omitted }
Suppose you merely clone the bucket array recursively, as we did for Stack: // Broken - results in shared internal state! @Override public HashTable clone() { try { HashTable result = (HashTable) super.clone(); result.buckets = buckets.clone(); return result; } catch (CloneNotSupportedException e) { throw new AssertionError(); } }
Though the clone has its own bucket array, this array references the same linked lists as the original, which can easily cause nondeterministic behavior in both the clone and the original. To fix this problem, youâ&#x20AC;&#x2122;ll have to copy the linked list that comprises each bucket individually. Here is one common approach: public class HashTable implements Cloneable { private Entry[] buckets = ...; private static class Entry { final Object key; Object value; Entry next; Entry(Object key, Object value, Entry next) { this.key = key; this.value = value; this.next = next; }
effective-java.book Page 59 Wednesday, April 23, 2008 4:18 AM
ITEM 11: OVERRIDE CLONE JUDICIOUSLY
// Recursively copy the linked list headed by this Entry Entry deepCopy() { return new Entry(key, value, next == null ? null : next.deepCopy()); } } @Override public HashTable clone() { try { HashTable result = (HashTable) super.clone(); result.buckets = new Entry[buckets.length]; for (int i = 0; i < buckets.length; i++) if (buckets[i] != null) result.buckets[i] = buckets[i].deepCopy(); return result; } catch (CloneNotSupportedException e) { throw new AssertionError(); } } ... // Remainder omitted }
The private class HashTable.Entry has been augmented to support a “deep copy” method. The clone method on HashTable allocates a new buckets array of the proper size and iterates over the original buckets array, deep-copying each nonempty bucket. The deep-copy method on Entry invokes itself recursively to copy the entire linked list headed by the entry. While this technique is cute and works fine if the buckets aren’t too long, it is not a good way to clone a linked list because it consumes one stack frame for each element in the list. If the list is long, this could easily cause a stack overflow. To prevent this from happening, you can replace the recursion in deepCopy with iteration: // Iteratively copy the linked list headed by this Entry Entry deepCopy() { Entry result = new Entry(key, value, next); for (Entry p = result; p.next != null; p = p.next) p.next = new Entry(p.next.key, p.next.value, p.next.next); return result; }
A final approach to cloning complex objects is to call super.clone, set all of the fields in the resulting object to their virgin state, and then call higher-level methods to regenerate the state of the object. In the case of our HashTable exam-
59
effective-java.book Page 60 Wednesday, April 23, 2008 4:18 AM
60
CHAPTER 3
METHODS COMMON TO ALL OBJECTS
ple, the buckets field would be initialized to a new bucket array, and the put(key, value) method (not shown) would be invoked for each key-value mapping in the hash table being cloned. This approach typically yields a simple, reasonably elegant clone method that generally doesn’t run quite as fast as one that directly manipulates the innards of the object and its clone. Like a constructor, a clone method should not invoke any nonfinal methods on the clone under construction (Item 17). If clone invokes an overridden method, this method will execute before the subclass in which it is defined has had a chance to fix its state in the clone, quite possibly leading to corruption in the clone and the original. Therefore the put(key, value) method discussed in the previous paragraph should be either final or private. (If it is private, it is presumably the “helper method” for a nonfinal public method.) Object’s clone method is declared to throw CloneNotSupportedException, but overriding clone methods can omit this declaration. Public clone methods should omit it because methods that don’t throw checked exceptions are easier to use (Item 59). If a class that is designed for inheritance (Item 17) overrides clone, the overriding method should mimic the behavior of Object.clone: it should be declared protected, it should be declared to throw CloneNotSupportedException, and the class should not implement Cloneable. This gives subclasses the freedom to implement Cloneable or not, just as if they extended Object directly. One more detail bears noting. If you decide to make a thread-safe class implement Cloneable, remember that its clone method must be properly synchronized just like any other method (Item 66). Object’s clone method is not synchronized, so even if it is otherwise satisfactory, you may have to write a synchronized clone method that invokes super.clone(). To recap, all classes that implement Cloneable should override clone with a public method whose return type is the class itself. This method should first call super.clone and then fix any fields that need to be fixed. Typically, this means copying any mutable objects that comprise the internal “deep structure” of the object being cloned, and replacing the clone’s references to these objects with references to the copies. While these internal copies can generally be made by calling clone recursively, this is not always the best approach. If the class contains only primitive fields or references to immutable objects, then it is probably the case that no fields need to be fixed. There are exceptions to this rule. For example, a field representing a serial number or other unique ID or a field representing the object’s creation time will need to be fixed, even if it is primitive or immutable. Is all this complexity really necessary? Rarely. If you extend a class that implements Cloneable, you have little choice but to implement a well-behaved
effective-java.book Page 61 Wednesday, April 23, 2008 4:18 AM
ITEM 11: OVERRIDE CLONE JUDICIOUSLY
clone method.
Otherwise, you are better off providing an alternative means of object copying, or simply not providing the capability. For example, it doesn’t make sense for immutable classes to support object copying, because copies would be virtually indistinguishable from the original. A fine approach to object copying is to provide a copy constructor or copy factory. A copy constructor is simply a constructor that takes a single argument whose type is the class containing the constructor, for example, public Yum(Yum yum);
A copy factory is the static factory analog of a copy constructor: public static Yum newInstance(Yum yum);
The copy constructor approach and its static factory variant have many advantages over Cloneable/clone: they don’t rely on a risk-prone extralinguistic object creation mechanism; they don’t demand unenforceable adherence to thinly documented conventions; they don’t conflict with the proper use of final fields; they don’t throw unnecessary checked exceptions; and they don’t require casts. While it is impossible to put a copy constructor or factory in an interface, Cloneable fails to function as an interface because it lacks a public clone method. Therefore you aren’t giving up interface functionality by using a copy constructor or factory in preference to a clone method. Furthermore, a copy constructor or factory can take an argument whose type is an interface implemented by the class. For example, by convention all generalpurpose collection implementations provide a constructor whose argument is of type Collection or Map. Interface-based copy constructors and factories, more properly known as conversion constructors and conversion factories, allow the client to choose the implementation type of the copy rather than forcing the client to accept the implementation type of the original. Suppose you have a HashSet s, and you want to copy it as a TreeSet. The clone method can’t offer this functionality, but it’s easy with a conversion constructor: new TreeSet(s). Given all of the problems associated with Cloneable, it’s safe to say that other interfaces should not extend it, and that classes designed for inheritance (Item 17) should not implement it. Because of its many shortcomings, some expert programmers simply choose never to override the clone method and never to invoke it except, perhaps, to copy arrays. If you design a class for inheritance, be aware that if you choose not to provide a well-behaved protected clone method, it will be impossible for subclasses to implement Cloneable.
61
effective-java.book Page 62 Wednesday, April 23, 2008 4:18 AM
62
CHAPTER 3
METHODS COMMON TO ALL OBJECTS
Item 12: Consider implementing Comparable Unlike the other methods discussed in this chapter, the compareTo method is not declared in Object. Rather, it is the sole method in the Comparable interface. It is similar in character to Objectâ&#x20AC;&#x2122;s equals method, except that it permits order comparisons in addition to simple equality comparisons, and it is generic. By implementing Comparable, a class indicates that its instances have a natural ordering. Sorting an array of objects that implement Comparable is as simple as this: Arrays.sort(a);
It is similarly easy to search, compute extreme values, and maintain automatically sorted collections of Comparable objects. For example, the following program, which relies on the fact that String implements Comparable, prints an alphabetized list of its command-line arguments with duplicates eliminated: public class WordList { public static void main(String[] args) { Set<String> s = new TreeSet<String>(); Collections.addAll(s, args); System.out.println(s); } }
By implementing Comparable, you allow your class to interoperate with all of the many generic algorithms and collection implementations that depend on this interface. You gain a tremendous amount of power for a small amount of effort. Virtually all of the value classes in the Java platform libraries implement Comparable. If you are writing a value class with an obvious natural ordering, such as alphabetical order, numerical order, or chronological order, you should strongly consider implementing the interface: public interface Comparable<T> { int compareTo(T t); }
The general contract of the compareTo method is similar to that of equals: Compares this object with the specified object for order. Returns a negative integer, zero, or a positive integer as this object is less than, equal to, or greater than the specified object. Throws ClassCastException if the specified objectâ&#x20AC;&#x2122;s type prevents it from being compared to this object.
effective-java.book Page 63 Wednesday, April 23, 2008 4:18 AM
ITEM 12: CONSIDER IMPLEMENTING COMPARABLE
In the following description, the notation sgn(expression) designates the mathematical signum function, which is defined to return -1, 0, or 1, according to whether the value of expression is negative, zero, or positive. • The implementor must ensure sgn(x.compareTo(y)) == -sgn(y.compareTo(x)) for all x and y. (This implies that x.compareTo(y) must throw an exception if and only if y.compareTo(x) throws an exception.) • The implementor must also ensure that the relation is transitive: (x.compareTo(y) > 0 && y.compareTo(z) > 0) implies x.compareTo(z) > 0. • Finally, the implementor must ensure that x.compareTo(y) == 0 implies that sgn(x.compareTo(z)) == sgn(y.compareTo(z)), for all z. • It is strongly recommended, but not strictly required, that (x.compareTo(y) == 0) == (x.equals(y)). Generally speaking, any class that implements the Comparable interface and violates this condition should clearly indicate this fact. The recommended language is “Note: This class has a natural ordering that is inconsistent with equals.” Don’t be put off by the mathematical nature of this contract. Like the equals contract (Item 8), this contract isn’t as complicated as it looks. Within a class, any reasonable ordering will satisfy it. Across classes, compareTo, unlike equals, doesn’t have to work: it is permitted to throw ClassCastException if two object references being compared refer to objects of different classes. Usually, that is exactly what compareTo should do, and what it will do if the class is properly parameterized. While the contract doesn’t preclude interclass comparisons, there are, as of release 1.6, no classes in the Java platform libraries that support them. Just as a class that violates the hashCode contract can break other classes that depend on hashing, a class that violates the compareTo contract can break other classes that depend on comparison. Classes that depend on comparison include the sorted collections TreeSet and TreeMap, and the utility classes Collections and Arrays, which contain searching and sorting algorithms. Let’s go over the provisions of the compareTo contract. The first provision says that if you reverse the direction of a comparison between two object references, the expected thing happens: if the first object is less than the second, then the second must be greater than the first; if the first object is equal to the second, then the second must be equal to the first; and if the first object is greater than the second, then the second must be less than the first. The second provision says that if one object is greater than a second, and the second is greater than a third, then the first must be greater than the third. The final provision says that all objects that compare as equal must yield the same results when compared to any other object.
63
effective-java.book Page 64 Wednesday, April 23, 2008 4:18 AM
64
CHAPTER 3
METHODS COMMON TO ALL OBJECTS
One consequence of these three provisions is that the equality test imposed by a compareTo method must obey the same restrictions imposed by the equals contract: reflexivity, symmetry, and transitivity. Therefore the same caveat applies: there is no way to extend an instantiable class with a new value component while preserving the compareTo contract, unless you are willing to forgo the benefits of object-oriented abstraction (Item 8). The same workaround applies, too. If you want to add a value component to a class that implements Comparable, don’t extend it; write an unrelated class containing an instance of the first class. Then provide a “view” method that returns this instance. This frees you to implement whatever compareTo method you like on the second class, while allowing its client to view an instance of the second class as an instance of the first class when needed. The final paragraph of the compareTo contract, which is a strong suggestion rather than a true provision, simply states that the equality test imposed by the compareTo method should generally return the same results as the equals method. If this provision is obeyed, the ordering imposed by the compareTo method is said to be consistent with equals. If it’s violated, the ordering is said to be inconsistent with equals. A class whose compareTo method imposes an order that is inconsistent with equals will still work, but sorted collections containing elements of the class may not obey the general contract of the appropriate collection interfaces (Collection, Set, or Map). This is because the general contracts for these interfaces are defined in terms of the equals method, but sorted collections use the equality test imposed by compareTo in place of equals. It is not a catastrophe if this happens, but it’s something to be aware of. For example, consider the BigDecimal class, whose compareTo method is inconsistent with equals. If you create a HashSet instance and add new BigDecimal("1.0") and new BigDecimal("1.00"), the set will contain two elements because the two BigDecimal instances added to the set are unequal when compared using the equals method. If, however, you perform the same procedure using a TreeSet instead of a HashSet, the set will contain only one element because the two BigDecimal instances are equal when compared using the compareTo method. (See the BigDecimal documentation for details.) Writing a compareTo method is similar to writing an equals method, but there are a few key differences. Because the Comparable interface is parameterized, the compareTo method is statically typed, so you don’t need to type check or cast its argument. If the argument is of the wrong type, the invocation won’t even compile. If the argument is null, the invocation should throw a NullPointerException, and it will, as soon as the method attempts to access its members.
effective-java.book Page 65 Wednesday, April 23, 2008 4:18 AM
ITEM 12: CONSIDER IMPLEMENTING COMPARABLE
The field comparisons in a compareTo method are order comparisons rather than equality comparisons. Compare object reference fields by invoking the compareTo method recursively. If a field does not implement Comparable, or you need to use a nonstandard ordering, you can use an explicit Comparator instead. Either write your own, or use a preexisting one as in this compareTo method for the CaseInsensitiveString class in Item 8. public final class CaseInsensitiveString implements Comparable<CaseInsensitiveString> { public int compareTo(CaseInsensitiveString cis) { return String.CASE_INSENSITIVE_ORDER.compare(s, cis.s); } ... // Remainder omitted }
Note that the CaseInsensitiveString class implements Comparable<CaseInsensitiveString>. This means that a CaseInsensitiveString reference can be compared only to other CaseInsensitiveString references. It is the normal pattern to follow when declaring a class to implement Comparable. Note also that the parameter of the compareTo method is a CaseInsensitiveString, not an Object. This is required by the aforementioned class declaration. Compare integral primitive fields using the relational operators < and >. For floating-point fields, use Double.compare or Float.compare in place of the relational operators, which do not obey the general contract for compareTo when applied to floating point values. For array fields, apply these guidelines to each element. If a class has multiple significant fields, the order in which you compare them is critical. You must start with the most significant field and work your way down. If a comparison results in anything other than zero (which represents equality), youâ&#x20AC;&#x2122;re done; just return the result. If the most significant fields are equal, go on to compare the next-most-significant fields, and so on. If all fields are equal, the objects are equal; return zero. The technique is demonstrated by this compareTo method for the PhoneNumber class in Item 9: public int compareTo(PhoneNumber pn) { // Compare area codes if (areaCode < pn.areaCode) return -1; if (areaCode > pn.areaCode) return 1;
65
effective-java.book Page 66 Wednesday, April 23, 2008 4:18 AM
66
CHAPTER 3
METHODS COMMON TO ALL OBJECTS
// Area codes are equal, compare prefixes if (prefix < pn.prefix) return -1; if (prefix > pn.prefix) return 1; // Area codes and prefixes are equal, compare line numbers if (lineNumber < pn.lineNumber) return -1; if (lineNumber > pn.lineNumber) return 1; return 0;
// All fields are equal
}
While this method works, it can be improved. Recall that the contract for comdoes not specify the magnitude of the return value, only the sign. You can take advantage of this to simplify the code and probably make it run a bit faster: pareTo
public int compareTo(PhoneNumber pn) { // Compare area codes int areaCodeDiff = areaCode - pn.areaCode; if (areaCodeDiff != 0) return areaCodeDiff; // Area codes are equal, compare prefixes int prefixDiff = prefix - pn.prefix; if (prefixDiff != 0) return prefixDiff; // Area codes and prefixes are equal, compare line numbers return lineNumber - pn.lineNumber; }
This trick works fine here but should be used with extreme caution. Don’t use it unless you’re certain the fields in question are non-negative or, more generally, that the difference between the lowest and highest possible field values is less than or equal to Integer.MAX_VALUE (231-1). The reason this trick doesn’t always work is that a signed 32-bit integer isn’t big enough to hold the difference between two arbitrary signed 32-bit integers. If i is a large positive int and j is a large negative int, (i - j) will overflow and return a negative value. The resulting compareTo method will return incorrect results for some arguments and violate the first and second provisions of the compareTo contract. This is not a purely theoretical problem: it has caused failures in real systems. These failures can be difficult to debug, as the broken compareTo method works properly for most input values.
CHAPTER 15
Service Oriented Architecture and the Business Tier
BUY
from informIT and
SAVE 35%
Enter the coupon code JAVAONE10 during checkout.
Google Bookmarks
Delicious
Digg
StumbleUpon
Implementing SOA Using Java EE AUTHORS B.V. Kumar, Prakash Narayan, and Tony Ng
FORMATS & ISBNs Book: 9780321492159 eBook: 9780137061556 Safari Books Online: 9780137151585
This book brings together all the practical insight you need to successfully architect enterprise solutions and implement them using SOA and Java EE technologies. Writing for senior IT developers, strategists, and enterprise architects, the authors cover everything from concepts to implementation, requirements to tools. The authors first review the Java EE platform’s essential elements in the context of SOA and web services deployment, and demonstrate how Java EE has evolved into the world’s best open source solution for enterprise SOA. After discussing standards such
as SOAP, WSDL, and UDDI, they walk through implementing each key aspect of SOA with Java EE. Step by step, you’ll learn how to integrate service-oriented web and business components of Java EE technologies with the help of process-oriented standards such as BPEL/CDL into a coherent, tiered enterprise architecture that can deliver a full spectrum of business services. COVERAGE INCLUDES
tion, choreography, and other essential SOA concepts • B uilding an advanced web services infrastructure for implementing SOA • U sing Java Persistence API to provide for persistence • G etting started with Java Business Integration (JBI), the new open specification for delivering SOA • I mplementing SOA at the web and business tiers
• U sing Java EE technologies to simplify SOA implementation
• D eveloping, configuring, and deploying SOA systems with NetBeans IDE
• M astering messaging, service descriptions, registries, orchestra-
• C onstructing SOA systems with NetBeans SOA Pack
informit.com/learnjava
20_0321492153_ch15.fm Page 247 Friday, December 4, 2009 12:53 PM
15 Service Oriented Architecture and the Business Tier
Strategically developed business components deployed in application servers can successfully deliver services in the Enterprise framework. Session Enterprise JavaBeans (EJB) in the business containers can be considered suitable candidates for delivering services because these business components can host the appropriate business logic and related services. These session EJBs are also session builders and therefore deliver services that help in building the workflow. These session beans are capable of delivering services using SOA-based messages. Business tier design patterns further help to provide optimal servicebuilding capability to the Enterprise SOA. The business tier and the business components occupy a significant and strategic position in the Enterprise application. These components help in controlling the business logic and can address crucial operations such as transactions, data persistence, and so on. These business tier components are also capable of delivering web services. Session EJBs, for example, can be configured as web services endpoints to deliver service (or services). The new Java Platform, Enterprise Edition technologies enable easier, quicker ways to compose services as part of the SOA. The business methods defined in the session EJBs can now be exposed as web services, and these services can be suitably
247
20_0321492153_ch15.fm Page 248 Friday, December 4, 2009 12:53 PM
248
CHAPTER 15
SERVICE ORIENTED ARCHITECTURE AND THE BUSINESS TIER
invoked by partners/collaborators. This chapter outlines the steps for exposing the methods of an EJB as web services and describes some of the design patterns session EJB components for SOA.
Delivering Services Through the Business Tier The previous chapter detailed that enterprises with the intent to collaborate with partners and associates are realizing that the best way to meet that goal is through providing/availing services. Many of these services operate on business information as a part of meeting the service demands. The role of the business tier, therefore, is critical in SOA for any Enterprise. The EJB specifications of the Java EE technology help develop business components that can render the appropriate services. These components encapsulate business and data access logic capability to the services. The EJB technology ensures that the services are scalable, secure, robust, and transaction-oriented in nature. Furthermore, these business components are portable, reusable, and container-managed. Recall from Chapter 9, “Java Platform, Enterprise Edition Overview,” that there are three varieties of EJBs: Session EJB, Entity EJB, and Message-Driven Bean (MDB). The roles and responsibilities of these business components are different and meet different needs of the enterprise business services and business processes. Here, we discuss how business components of the Java Platform, Enterprise Edition provide services as part of SOA.
Business Tier Overview Figure 14.1 illustrated the overall picture of the tiered structure of the new Java EE technology scenario, complete with different tiers and components communicating with each other. The focus here is the business tier. (The ways and the means in which the client communicates with the presentation tier are detailed in Chapters 10, “Web Technologies in Java EE,” and Chapter 14, “Service Oriented Architecture and the Web Tier.” Please refer to those chapters to review, if necessary.) In this chapter, we focus on the communication of presentation tier components with the business components and analyze how they help in managing the business goals of the Enterprise, as part of the service orientation.
20_0321492153_ch15.fm Page 249 Friday, December 4, 2009 12:53 PM
249
DELIVERING SERVICES THROUGH THE BUSINESS TIER
Among these three business components, we see that the session EJBs are ideally suited for providing services. Since their inception, session beans have been used for a variety of session-oriented business operations such as online banking, online shopping, and so on, and these operations become a perfect fit for exposure as web services. Furthermore, these EJBs have already been used to accomplish transaction-oriented business operations by accessing a persistence layer such as Entity EJBs or an ORM framework such as TopLink, or even sending a message to a Java Message Service (JMS) queue that would, in turn, activate an MDB to process the business logic. These session EJBs become a natural web services gateway. The business methods defined in the session EJBs, indicated as the Web Service endpoints in Figure 15.1, can now be â&#x20AC;&#x153;exposedâ&#x20AC;? as web services so that partners/collaborators can invoke these web services as part of Business-to-Business (B2B) transaction. Web Service Endpoints
Client Tier
Presentation Tier
Entity EJBs or POJOs
Business Tier
Resource Tier
Session EJBs
Figure 15.1 The business tier in the new Java Platform, Enterprise Edition technology
When a web service on the session EJBs is invoked, it can render the services by carrying out the appropriate business task and returning the response to the service invoker. The service invocation is a SOAP request message to the session EJB. The response to the service consumer is a SOAP response message. Before the SOAP response message is constructed, the session EJB will carry out the business transaction that can involve things such as data persistence, message delivery to a JMS queue, and so on. When performing the data persistence, the session EJB can interact with the entity EJBs or the new POJOs for carrying out
20_0321492153_ch15.fm Page 250 Friday, December 4, 2009 12:53 PM
250
CHAPTER 15
SERVICE ORIENTED ARCHITECTURE AND THE BUSINESS TIER
the data persistence operation. These EJBs can directly communicate with the underlying database for carrying out the data persistence operations. Because there are a myriad of possibilities of service invocation sequence from the client and business components performing business tasks, it is critical to exercise proper care in architecting the Enterprise solution for optimum performance. Architecting the Enterprise application for service orientation, along with designing the tiers, components, and communication for meeting the quality of service (QoS) standards, helps Enterprises transact business in a normal, satisfactory fashion. Design patterns in the field of software architecture and design have gained increased importance over time. Patterns basically reflect the experience, knowledge, and insights of developers who have successfully used and cataloged these patterns to share them with the developer community. Although these patterns are reusable, they need to be properly adapted to the current needs of the Enterprise problems. If the business tier is present in the architecture of an Enterprise application, it likely plays a critical role in meeting the needs of the Enterprise. It is therefore necessary to apply appropriate design patterns in this tier so that the application behaves expectedly and smoothly during the operation. In the next section, we explore the variety of design patterns that can be applied to the business tier.
Business Tier Design Patterns and SOA Unlike the presentation tier components, the business tier components are not directly exposed to clients. Clients normally establish connection to these business components via the web tier components. For example, when a client intends to transact with the business components, he initiates an HTTP session with the presentation tier components such as Servlets and JSP. These components build a session for the client and then route the communication to the business tier components. The business tier components, in turn, carry out the intended task/transaction. This transaction could be leading to persisting the data in a database or triggering a sequence of persistence tasks in the EIS tier. The increased popularity of the web services, along with the requirement to reorient the services for SOA, led to changes in the EJB specifications. These changes enabled the business components to be web services-oriented. The previous J2EE specification, J2EE 1.4, focused heavily on the world of web services. At that time, it was a JAX-RPC route to define web services and enable the EJB components to exchange SOAP messages via remote procedure call (RPC) communication mode.
20_0321492153_ch15.fm Page 251 Friday, December 4, 2009 12:53 PM
BUSINESS TIER DESIGN PATTERNS
The new Java EE technology and specifications have eased the development and deployment of the business tier components. Now, these components are not only lightweight and efficient, they are also easier to develop and deploy. These components can also persist the data, carry out important business transaction tasks, and participate in the workflow of the Enterprise. The current version of Java EE technology application servers and tools also supports the components developed for the earlier versions of the Java Enterprise Edition technologies. Moreover, the new components can coexist and communicate with earlier versions of the components. This option enables the developers to transition to the newer technology easily and quickly. It also helps the enterprise application smoothly transition to the newer technology.
Business Tier Design Patterns Typically, the presentation tier components act as the facade to the business tier components. In fact, facade is a pattern in the presentation tier. Likewise, the business tier components communicate with the EIS integration tier components and resource adapters for persisting the information. Moreover, a number of business components such as EJBs, POJOs, JavaBeans, helper classes, utility classes, and so on are typically present in the business containers. This situation also demands that patterns be incorporated within the business tier so the components in the business tier function smoothly and efficiently while delivering services. The following are the most common business tier design patterns: â&#x20AC;˘ Presentation tier-to-business tier design patterns â&#x20AC;˘ Integration tier design patterns â&#x20AC;˘ Business tier patterns In the next section, we briefly discuss the first two patterns, and later in the chapter we explore business tier design patterns.
Presentation Tier-to-Business Tier Design Patterns Recall that the presentation tier acts as a facade to the business tier. Any service components in the business tier are accessible to the presentation tier components only. Components within the web tier frameworks, such as Struts or JSF, invoke the service request on the business tier service components. The overall architecture of the presentation tier-to-business tier design pattern is depicted in Figure 15.2. The component-level interaction among different components between presentation and business tiers are depicted as a class
251
20_0321492153_ch15.fm Page 252 Friday, December 4, 2009 12:53 PM
252
CHAPTER 15
SERVICE ORIENTED ARCHITECTURE AND THE BUSINESS TIER
diagram in Figure 15.3. We have focused only on the parts relevant to our discussion. Also, the components that are shown only represent the typical components that are present in that tier. Furthermore, this diagram is conceptual in nature. Different design patterns belonging to this category can be suitably mapped on to this figure to reveal this class diagram for the specific design pattern in question.1 Service Layer
JSP
EJBs
POJOs Helper and Utility Classes
Servlets
Helper and Utility Classes
Presentation Tier
Business Tier
Figure 15.2 The presentation tier-to-business tier scenario
There are four major patterns that belong to this category of design patterns: • • • •
Session facade pattern Business delegate pattern Transfer object pattern Service locater pattern
To illustrate the behavior of different components in the presentation tier and business tier, we will choose the transfer object as a typical design pattern.
Transfer Object Design Pattern Transfer objects are simple POJOs that transport data across the different tiers. In this particular design pattern, the data transport is between the presentation tier and the business tier. The main advantages of transfer objects are as follows:
20_0321492153_ch15.fm Page 253 Friday, December 4, 2009 12:53 PM
253
BUSINESS TIER DESIGN PATTERNS
â&#x20AC;˘ They reduce coupling between the presentation layer and the business layer. â&#x20AC;˘ They reduce the number of remote method calls, which are expensive. To reduce the coupling between the presentation tier components and the business tier components, it would be advisable to avoid communicating with these business objects directly on the presentation layer. Introducing a transfer object helps in reducing this coupling. The interaction between different components in the presentation tier and business tier can be better expressed with the help of a component diagram and sequence diagram. The component diagram for the transfer object design pattern is represented in Figure 15.3. creates / uses
<<POJO>> Client
Component
accesses 1..*
1..*
Transfer Object creates / uses
<<Presentation Tier>>
<<Business Tier>>
<<Resource Tier>>
Web Components
Business Components
Integration Components
Figure 15.3 The class diagram showing the component-level interaction among different components
Here, the class named Client essentially represents the browser client. The class named Component essentially creates and uses the transfer objects that are, in essence, POJOs. These POJOs can get the data from any or all of the tiers by suitably interacting with the components. In this case, we have mapped the sequence of the interaction of different components in different tiers using a sequence diagram in Figure 15-4. For the sake of simplicity, we have shown the sequence of events that transpire between the client and the two transfer objects.
20_0321492153_ch15.fm Page 254 Friday, December 4, 2009 12:53 PM
254
CHAPTER 15
Client
SERVICE ORIENTED ARCHITECTURE AND THE BUSINESS TIER
Component
get data
TransferObject create
serialize and send
get value get value TransferObject create
serialize and send get value get value
Figure 15.4 The sequence diagram indicating the sequence of method invocation/ creation/use of different objects in a transfer object design pattern
In Figure 15.4, we see that the client is initiating the request on the component. The component, in this case, is creating two transfer objects that fetch the data from different tiers.
Integration Tier Design Patterns The overall architecture of the integration tier design pattern, in association with the business tier design pattern, is depicted in Figure 15.5. For our purposes, we have focused only on the relevant portions of the tiers involved, leaving out the finer details for the sake of brevity. Also, the components that are shown represent only the typical components that are present in that tier. Furthermore, this diagram is conceptual in nature, and different design patterns belonging to this category can be suitably mapped on to this figure to reveal this class diagram for the specific design pattern in question. These patterns can also be referred to as business tier-to-integration tier design patterns.
20_0321492153_ch15.fm Page 255 Friday, December 4, 2009 12:53 PM
255
INTEGRATION TIER DESIGN PATTERNS
There are four major design patterns in this tier: • • • •
Data access object (DAO) design pattern Service activator design pattern Domain store design pattern Web service broker design pattern Resource Adapters
Service Layer
Helper and Utility Classes
POJOs Helper and Utility Classes
Business Tier
EIS Integration Tier
Figure 15.5 The integration-tier pattern scenario
To illustrate the behavior of different components in the business tier and integration tier, we will choose DAO as a typical design pattern for detailed discussion in the next subsection. This pattern is very close to (as well as related to) the transfer object design pattern we discussed earlier in the “Presentation Tier-toBusiness Tier Design Patterns” section of this chapter.
The Data Access Object Pattern The class diagram of this pattern is shown in Figure 15.6. Client
uses
DataAccessObject
1
* +create:void +read:Object +update:void +delete:void
DataSource
accesses
1
uses creates
creates / uses creates 1
<<Transfer Object>> Data
ResultSet
Figure 15.6 Class diagram representing the data access object design pattern
20_0321492153_ch15.fm Page 256 Friday, December 4, 2009 12:53 PM
256
CHAPTER 15
SERVICE ORIENTED ARCHITECTURE AND THE BUSINESS TIER
The classes involved here are essentially the data source object, the DAO, and the data object itself. The DAO uses (looks up) the data source object to create the data, and this normally would be a result set from the database query. The DAO uses this result set object to extract the data. The DAO then creates the transfer object and populates the data. Ultimately, the client uses this transfer object. This sequence of events is illustrated in Figure 15.7. This figure presents just one set of interactions that result in the exemplification of the DAO design pattern. DataSource
Client
DataAccessObject create
lookup get data
open connection ResultSet
execute query create
get data create
<<TransferObject>> Data
set data close connection
get value
Figure 15.7 Class diagram representing the DAO design pattern
To review, the client creates the DAO, and the DAO performs a lookup to access the data source object. After the connection is established, the DAO executes a query on the data source object, resulting in the creation of a result set object. The DAO now creates the data object (which is the transfer object) and populates the fields from the result set object. This transfer object is now available to the client object.
20_0321492153_ch15.fm Page 257 Friday, December 4, 2009 12:53 PM
257
INTRABUSINESS TIER DESIGN PATTERNS
Intrabusiness Tier Design Patterns The overall architecture of the intrabusiness tier design pattern is depicted in Figure 15.8. For our purposes, we have focused only on the relevant components and portion of the tiers, leaving out the finer details for the sake of brevity. Also, the components that are shown represent only the typical components that are present in that tier. Furthermore, this diagram is conceptual in nature, and different design patterns belonging to this category can be suitably mapped on to this figure to reveal this class diagram for the specific design pattern in question. Session EJBs
Helper and Utility Classes
POJOs Helper and Utility Classes
Entity EJBs Resource Adapters
Presentation Tier
Business Tier
EIS Integration Tier
Figure 15.8 The intrabusiness tier design pattern scenario
Intrabusiness tier design patterns are basically the business tier design patterns as documented and cataloged elsewhere. The business tier design patterns assist in effectively handling business logic and data persistence functions effectively and efficiently. These design patterns also help improve performance, tighten security, and promote modularity to the Enterprise application. There are eight design patterns identified in this tier that help in achieving these QoS: • • • • • • • •
Business delegate Service locater Session facade Application service Business object Composite entity Transfer object Transfer object assembler
20_0321492153_ch15.fm Page 258 Friday, December 4, 2009 12:53 PM
258
SERVICE ORIENTED ARCHITECTURE AND THE BUSINESS TIER
CHAPTER 15
To illustrate the behavior of different components in the intrabusiness tier, we choose application service as a typical design pattern for a detailed discussion on the intrabusiness tier design pattern.
Application Service Design Pattern Application service design patterns essentially help centralize and aggregate behavior to provide a uniform service layer. This pattern essentially uses an application controller that invokes the business and data access methods from the business objects (BO) or DAO. The class diagram showing the interactions among different components in this design pattern is shown in Figure 15.9. invokes
Client
accesses 1..*
ApplicationController
invokes
DataAccessObject
1..*
invokes ServiceFacade
BusinessObject
Service
<<POJO>> Helper
Figure 15.9 Class diagram showing interactions among different components in application service design pattern
The client accesses the application controller, which now is a centralized repository to access business logic and services. Based on the requirement, the client invokes the BO, the DAO, or a service. The application controller is essentially the application service. There could be any number of application services deployed in the business tier. These application services handle the logic and service requests from the clients and process them accordingly. Consider a situation in which a client is attempting to access services from two applications: Application Service 1 and Application Service 2, as shown in Figure 15.10. Initially, the client invokes the Application Service 1, which sets things into motion. The Application Service 1 invokes a business method on Business Object 1 first and then on Business Object 2. Next, the client invokes Application Service 2 for services. This sets things into motion, this time, in Application Service 2, which invokes methods on DAO and another service object, respectively.
20_0321492153_ch15.fm Page 259 Friday, December 4, 2009 12:53 PM
259
SUMMARY
Application Service 1
Client invoke
Business Object 1
Business Object 2
Application Service 2
Service
DAO
Process
Process Process
Process Process
invoke
Process
invoke Process
invoke
Figure 15.10 Sequence diagram indicating the sequence of events taking place between the application controller and other business objects in the application service design pattern
Summary The business tier plays a significant role in the enterprise application tiers. The components in this tier not only control the business logic, they are capable of addressing critical operations, including transactions, data persistence, and so on. The arrival of web services technology and the motivation for service orientation have significantly influenced the functionality of the business components. These components are now ready for delivering web services. It is now quicker and easier than ever to create and deploy business components in the new Java EE technologies. The business services in these components can be exposed as web services. The new technology also supports the older business components created as per the earlier EJB specifications and can interact with the
20_0321492153_ch15.fm Page 260 Friday, December 4, 2009 12:53 PM
260
CHAPTER 15
SERVICE ORIENTED ARCHITECTURE AND THE BUSINESS TIER
new components, as well. If present in the overall architecture, the business tier significantly affects the QoS of the Enterprise application. It is, therefore, important to carefully consider business tier design patterns while designing the Enterprise solution for service orientation. In the next chapter, we describe briefly how the new Java Enterprise technology addresses the QoS aspects of the services in SOA. We essentially cover some of the important QoS aspects such as reliability, availability, and security and indicate how these nonfunctional requirements affect the nature of solution in service orientation.
Endnote 1. The patterns are usually presented using sequence or class diagrams. When we deal with specific patterns, we present the appropriate diagram.
CHAPTER 15
A Java Puzzlers Sample
BUY
from informIT and
SAVE 35%
Enter the coupon code JAVAONE10 during checkout.
Google Bookmarks
Delicious
Digg
StumbleUpon
Java™ Puzzlers: Traps, Pitfalls, and Corner Cases AUTHORS Joshua Bloch and Neal Gafter
FORMATS & ISBNs Book: 9780321336781 eBook: 9780321643506 “ Every programming language has its quirks. This lively book reveals oddities of the Java programming language through entertaining and thought-provoking programming puzzles.” — GUY STEELE, Sun Fellow and coauthor of The Java™ Language Specification
“ I laughed, I cried, I threw up (my hands in admiration).” — TIM PEIERLS, president, Prior Artisans LLC, and member of the JSR 166 Expert Groupn
How well do you really know Java? Are you a code sleuth? Have you ever spent days chasing a bug caused by a trap or pitfall in Java or its libraries? Do you like brainteasers? Then this is the book for you! In the tradition of Effective Java™, Bloch and Gafter dive deep into the subtleties of the Java programming language and its core libraries. Illustrated with visually stunning optical illusions, Java™ Puzzlers features 95 diabolical puzzles that educate and entertain. Anyone with a working knowledge of Java will understand the puzzles, but even the most seasoned veteran will find them challenging.
Most of the puzzles take the form of a short program whose behavior isn’t what it seems. Can you figure out what it does? Puzzles are grouped loosely according to the features they use, and detailed solutions follow each puzzle. The solutions go well beyond a simple explanation of the program’s behavior — they show you how to avoid the underlying traps and pitfalls for good. A handy catalog of traps and pitfalls at the back of the book provides a concise taxonomy for future reference.
Solve these puzzles and you’ll never again fall prey to the counterintuitive or obscure behaviors that can fool even the most experienced programmers.
informit.com/learnjava
puzzlers.book Page 1 Thursday, April 13, 2006 8:35 AM
1 Introduction This book is filled with brainteasers about the Java programming language and its core libraries. Anyone with a working knowledge of Java can understand these puzzles, but many of them are tough enough to challenge even the most experienced programmer. Don’t feel bad if you can’t solve them. They are grouped loosely according to the features they use, but don’t assume that the trick to a puzzle is related to its chapter heading; we reserve the right to mislead you. Most of the puzzles exploit counterintuitive or obscure behaviors that can lead to bugs. These behaviors are known as traps, pitfalls, and corner cases. Every platform has them, but Java has far fewer than other platforms of comparable power. The goal of the book is to entertain you with puzzles while teaching you to avoid the underlying traps and pitfalls. By working through the puzzles, you will become less likely to fall prey to these dangers in your code and more likely to spot them in code that you are reviewing or revising. This book is meant to be read with a computer at your side. You’ll need a Java development environment, such as Sun’s JDK [JDK-5.0]. It should support release 5.0, as some of the puzzles rely on features introduced in this release. You can download the source code for the puzzles from www.javapuzzlers.com. Unless you’re a glutton for punishment, we recommend that you do this before solving the puzzles. It’s a heck of a lot easier than typing them in yourself.
1
puzzlers.book Page 2 Thursday, June 9, 2005 10:37 PM
2
Chapter 1 • Introduction
Most of the puzzles take the form of a short program that appears to do one thing but actually does something else. It’s your job to figure out what the program does. To get the most out of these puzzles, we recommend that you take this approach: 1. Study the program and try to predict its behavior without using a computer. If you don’t see a trick, keep looking. 2. Once you think you know what the program does, run it. Did it do what you thought it would? If not, can you come up with an explanation for the behavior you observed? 3. Think about how you might fix the program, assuming it is broken. 4. Then and only then, read the solution. Some of the puzzles require you to write a small amount of code. To get the most out of these puzzles, we recommend that you try—at least briefly—to solve them without using a computer, and then test your solution on a computer. If your code doesn’t work, play around with it and see whether you can make it work before reading the solution. Unlike most puzzle books, this one alternates between puzzles and their solutions. This allows you to read the book without flipping back and forth between puzzles and solutions. The book is laid out so that you must turn the page to get from a puzzle to its solution, so you needn’t fear reading a solution accidentally while you’re still trying to solve a puzzle. We encourage you to read each solution, even if you succeed in solving the puzzle. The solutions contain analysis that goes well beyond a simple explanation of the program’s behavior. They discuss the relevant traps and pitfalls, and provide lessons on how to avoid falling prey to these hazards. Like most best-practice guidelines, these lessons are not hard-and-fast rules, but you should violate them only rarely and with good reason. Most solutions contain references to relevant sections of The Java™ Language Specification, Third Edition [JLS]. These references aren’t essential to understanding the puzzles, but they are useful if you want to delve deeper into the language rules underlying the puzzles. Similarly, many solutions contain references to relevant items in Effective Java™ Programming Language Guide [EJ]. These references are useful if you want to delve deeper into best practices. Some solutions contain discussions of the language or API design decisions that led to the danger illustrated by the puzzle. These “lessons for language
puzzlers.book Page 3 Thursday, April 13, 2006 8:35 AM
Chapter 1 • Introduction
designers” are meant only as food for thought and, like other food, should be taken with a grain of salt. Language design decisions cannot be made in isolation. Every language embodies thousands of design decisions that interact in subtle ways. A design decision that is right for one language may be wrong for another. Many of the traps and pitfalls in these puzzles are amenable to automatic detection by static analysis: analyzing programs without running them. Some excellent tools are available for detecting bugs by static analysis, such as Bill Pugh and David Hovemeyer’s FindBugs [Hovemeyer04]. Some compilers and IDEs, such as Jikes and Eclipse, perform bug detection as well [Jikes, Eclipse]. If you are using one of these compilers, it is especially important that you not compile a puzzle until you’ve tried to solve it: The compiler’s warning messages may give away the solution. Appendix A of this book is a catalog of the traps and pitfalls in the Java platform. It provides a concise taxonomy of the anomalies exploited by the puzzles, with references back to the puzzles and to other relevant resources. Do not look at the appendix until you’re done solving the puzzles. Reading the appendix first would take all the fun out of the puzzles. After you’ve finished the puzzles, though, this is the place you’ll turn to for reference. Appendix B describes the optical illusions that decorate the book. This appendix explains the nature of each illusion and identifies the inventor, if known. It provides many bibliographic references. This appendix is the place to go if you aren’t sure why a given drawing constitutes an illusion, or if you simply want to know more about one of the illusions.
3
puzzlers.book Page 13 Thursday, June 9, 2005 10:37 PM
Puzzle 5: The Joy of Hex
Puzzle 5: The Joy of Hex The following program adds two hexadecimal, or “hex,” literals and prints the result in hex. What does the program print? public class JoyOfHex { public static void main(String[] args) { System.out.println( Long.toHexString(0x100000000L + 0xcafebabe)); } }
13
puzzlers.book Page 14 Thursday, June 9, 2005 10:37 PM
14
Chapter 2 â&#x20AC;˘ Expressive Puzzlers
Solution 5: The Joy of Hex It seems obvious that the program should print 1cafebabe. After all, that is the sum of the hex numbers 10000000016 and cafebabe16. The program uses long arithmetic, which permits 16 hex digits, so arithmetic overflow is not an issue. Yet, if you ran the program, you found that it prints cafebabe, with no leading 1 digit. This output represents the low-order 32 bits of the correct sum, but somehow the thirty-third bit gets lost. It is as if the program were doing int arithmetic instead of long, or forgetting to add the first operand. Whatâ&#x20AC;&#x2122;s going on here? Decimal literals have a nice property that is not shared by hexadecimal or octal literals: Decimal literals are all positive [JLS 3.10.1]. To write a negative decimal constant, you use the unary negation operator (-) in combination with a decimal literal. In this way, you can write any int or long value, whether positive or negative, in decimal form, and negative decimal constants are clearly identifiable by the presence of a minus sign. Not so for hexadecimal and octal literals. They can take on both positive and negative values. Hex and octal literals are negative if their high-order bit is set. In this program, the number 0xcafebabe is an int constant with its high-order bit set, so it is negative. It is equivalent to the decimal value -889275714. The addition performed by the program is a mixed-type computation: The left operand is of type long, and the right operand is of type int. To perform the computation, Java promotes the int value to a long with a widening primitive conversion [JLS 5.1.2] and adds the two long values. Because int is a signed integral type, the conversion performs sign extension: It promotes the negative int value to a numerically equal long value. The right operand of the addition, 0xcafebabe, is promoted to the long value 0xffffffffcafebabeL. This value is then added to the left operand, which is 0x100000000L. When viewed as an int, the high-order 32 bits of the signextended right operand are -1, and the high-order 32 bits of the left operand are 1. Add these two values together and you get 0, which explains the absence of the leading 1 digit in the programâ&#x20AC;&#x2122;s output. Here is how the addition looks when done in longhand. (The digits at the top of the addition are carries.) 11 11 11 1
0xffffffffcafebabeL + 0x0000000100000000L 0x00000000cafebabeL
puzzlers.book Page 15 Thursday, June 9, 2005 10:37 PM
Puzzle 6: Multicast
Fixing the problem is as simple as using a long hex literal to represent the right operand. This avoids the damaging sign extension, and the program prints the expected result of 1cafebabe: public class JoyOfHex { public static void main(String[] args) { System.out.println( Long.toHexString(0x100000000L + 0xcafebabeL)); } }
The lesson of this puzzle is that mixed-type computations can be confusing, more so given that hex and octal literals can take on negative values without an explicit minus sign. To avoid this sort of difficulty, it is generally best to avoid mixed-type computations. For language designers, it is worth considering support for unsigned integral types, which eliminate the possibility of sign extension. One might argue that negative hex and octal literals should be prohibited, but this would likely frustrate programmers, who often use hex literals to represent values whose sign is of no significance.
15
character.fm Page 39 Thursday, April 20, 2006 10:17 PM
Puzzle 18: String Cheese
Puzzle 18: String Cheese This program creates a string from a sequence of bytes, then iterates over the characters in the string and prints them as numbers. Describe the sequence of numbers that the program prints: public class StringCheese { public static void main(String[] args) { byte[] bytes = new byte[256]; for(int i = 0; i < 256; i++) bytes[i] = (byte)i; String str = new String(bytes); for(int i = 0, n = str.length(); i < n; i++) System.out.print((int)str.charAt(i) + " "); } }
39
puzzlers.book Page 40 Thursday, June 9, 2005 10:37 PM
40
Chapter 3 • Puzzlers with Character
Solution 18: String Cheese First, the byte array is initialized with every possible byte value from 0 to 255. Then these byte values are translated into char values by the String constructor. Finally, the char values are cast to int values and printed. The printed values are guaranteed to be nonnegative, because char values are unsigned, so you might expect the program to print the integers from 0 to 255 in order. If you ran the program, maybe you saw this sequence. Then again, maybe you didn’t. We ran it on four machines and saw four different sequences, including the one described previously. This program isn’t even guaranteed to terminate normally, much less to print any particular sequence. Its behavior is completely unspecified. The culprit here is the String(byte[]) constructor. Its specification says: “Constructs a new String by decoding the specified byte array using the platform’s default charset. The length of the new String is a function of the charset, and hence may not be equal to the length of the byte array. The behavior of this constructor when the given bytes are not valid in the default charset is unspecified” [Java-API]. What exactly is a charset? Technically, it is “the combination of a coded character set and a character-encoding scheme” [Java-API]. In other words, a charset is a bunch of characters, the numerical codes that represent them, and a way to translate back and forth between a sequence of character codes and a sequence of bytes. The translation scheme differs greatly among charsets. Some have a one-toone mapping between characters and bytes; most do not. The only default charset that will make the program print the integers from 0 to 255 in order is ISO-8859-1, more commonly known as Latin-1 [ISO-8859-1]. A J2SE Runtime Environment’s default charset depends on the underlying operating system and locale. If you want to know your JRE’s default charset and you are using release 5.0 or a later release, you can find out by invoking java.nio.charset.Charset.defaultCharset(). If you are using an earlier release, you can find out by reading the system property "file.encoding". Luckily, you are not forced to put up with the vagaries of default charsets. When translating between char sequences and byte sequences, you can and usually should specify a charset explicitly. A String constructor that takes a charset name in addition to a byte array is provided for this purpose. If you replace the String constructor invocation in the original program with the one
puzzlers.book Page 41 Thursday, June 9, 2005 10:37 PM
Puzzle 19: Classy Fire
that follows, the program is guaranteed to print the integers from 0 to 255 in order, regardless of the default charset: String str = new String(bytes, "ISO-8859-1");
This constructor is declared to throw UnsupportedEncodingException, so you must catch it or, preferably, declare the main method to throw it, or the program wonâ&#x20AC;&#x2122;t compile. The program wonâ&#x20AC;&#x2122;t actually throw the exception, though. The specification for Charset mandates that every implementation of the Java platform support certain charsets, and ISO-8859-1 is among them. The lesson of this puzzle is that every time you translate a byte sequence to a String, you are using a charset, whether you specify it explicitly or not. If you want your program to behave predictably, specify a charset each time you use one. For API designers, perhaps it was not such a good idea to provide a String(byte[]) constructor that depends on the default charset.
41
puzzlers.book Page 65 Thursday, June 9, 2005 10:37 PM
Puzzle 30: Son of Looper
Puzzle 30: Son of Looper Provide a declaration for i that turns this loop into an infinite loop: while (i != i + 0) { }
Unlike previous loopers, you must not use floating-point in your answer. In other words, you must not declare i to be of type double or float.
65
puzzlers.book Page 66 Thursday, June 9, 2005 10:37 PM
66
Chapter 4 • Loopy Puzzlers
Solution 30: Son of Looper Like the previous puzzle, this one seems impossible at first glance. After all, a number is always equal to itself plus 0, and you were forbidden from using floating-point, so you can’t use NaN. There is no NaN equivalent for the integral types. What gives? The inescapable conclusion is that the type of i must be non-numeric, and therein lies the solution. The only non-numeric type for which the + operator is defined is String. The + operator is overloaded: For the String type, it performs not addition but string concatenation. If one operand in the concatenation is of some type other than String, that operand is converted to a string prior to concatenation [JLS 15.18.1]. In fact, i can be initialized to any value so long as it is of type String; for example: String i = "Buy seventeen copies of Effective Java!";
The int value 0 is converted to the String value "0" and appended to the blatant plug. The resulting string is not equal to the original as computed by the equals method, so it certainly can’t be identical, as computed by the == operator. Therefore, the boolean expression (i != i + 0) evaluates to true and the loop never terminates. In summary, operator overloading can be very misleading. The plus sign in the puzzle looks like addition, but it is made to perform string concatenation by choosing the correct type for the variable i, which is String. The puzzle is made even more misleading because the variable is named i, a name that is usually reserved for integer variables. Good variable, method, and class names are at least as important to program readability as good comments. The lesson for language designers is the same as in Puzzles 11 and 13. Operator overloading can be confusing. Perhaps the + operator should not have been overloaded for string concatenation. It may well be worth providing a string concatenation operator, but it doesn’t have to be +.
puzzlers.book Page 101 Thursday, June 9, 2005 10:37 PM
Puzzle 45: Exhausting Workout
Puzzle 45: Exhausting Workout This puzzle tests your knowledge of recursion. What does this program do? public class Workout { public static void main(String[] args) { workHard(); System.out.println("Itâ&#x20AC;&#x2122;s nap time."); } private static void workHard() { try { workHard(); } finally { workHard(); } } }
101
puzzlers.book Page 102 Thursday, June 9, 2005 10:37 PM
102
Chapter 5 • Exceptional Puzzlers
Solution 45: Exhausting Workout If it weren’t for the try-finally statement, it would be obvious what this program does: The workHard method calls itself recursively until the program throws a StackOverflowError, at which point it terminates with an uncaught exception. The try-finally statement complicates matters. When it tries to throw a StackOverflowError, the program ends up in a finally block in the workHard method, where it calls itself recursively. This seems like a prescription for an infinite loop. Does the program loop indefinitely? If you run it, it appears to do exactly that, but the only way to know for sure is to analyze its behavior. The Java virtual machine limits the stack depth to some preset level. When this level is exceeded, the VM throws a StackOverflowError. In order to make it easier to think about the behavior of the program, let’s pretend that the stack depth limit is much smaller than it really is: say, 3. Now let’s trace the execution. The main method calls workHard, which calls itself recursively from its try block. Again it calls itself from its try block. At this point, the stack depth is 3. When the workHard method attempts once more to call itself from its try block, the call fails immediately with a StackOverflowError. This error is caught in the innermost finally block, where the stack depth is already 3. From there the workHard method attempts to call itself recursively, but the call fails with a StackOverflowError. This error is caught in the finally block one level up, where the stack depth is 2. The call from this finally block has the same behavior as the call from the corresponding try block: It results eventually in a StackOverflowError. A pattern appears to be emerging, and indeed it is. The execution of WorkOut is illustrated in Figure 5.1. In this figure, calls to workHard are represented by arrows, and executions of workHard are represented by circles. All calls are recursive except for first one. Calls that result in an immediate StackOverflowError are represented by arrows leading to gray circles. Calls from try blocks are represented by arrows pointing down and to the left, and calls from finally blocks are represented by arrows pointing down and to the right. The numbers on the arrows describe the sequence of calls. The figure shows one call from depth 0—the call from main—two calls from depth 1, four calls from depth 2, and eight calls from depth 3, a total of fifteen calls. The eight calls from depth 3 each result in an immediate StackOverflowError. At least on a VM that limits the stack depth to 3, the program is not an infinite loop: It terminates after fifteen calls and eight exceptions.
puzzlers.book Page 103 Thursday, April 13, 2006 8:35 AM
Puzzle 45: Exhausting Workout
But what about on a real VM? It still isn’t an infinite loop. The call diagram looks just like the one in Figure 5.1 only much, much bigger. Call from depth 0
1
1
2
2
3
Call from main method Call from try block Call from finally block n Call sequence number Execution of workHard
main
4
Figure 5.1
3
6
5
7
StackOverflowError
9
10
8
11
12
13
14
15
The execution of Workout.
How much bigger? A quick experiment shows that many VMs limit stack depth to 1,024. The number of calls is therefore 1 + 2 + 4 + 8 … + 21,024 = 21,025 − 1. The number of exceptions thrown is 21,024. Let’s assume that our machine can execute 1010 calls per second and generate 1010 exceptions per second, which is quite generous by current standards. Under these assumptions, the program will terminate in about 1.7 × 10291 years. To put this in perspective, the lifetime of our sun is estimated at 1010 years, so it is a safe bet that none of us will be around to see this program terminate. Although it isn’t an infinite loop, it might as well be. Technically speaking, the call diagram is a complete binary tree whose depth is the stack depth limit of the VM. The execution of the Workout program amounts to a preorder traversal of this tree. In a preorder traversal, the program visits a node and then recursively visits its left and right subtrees. One call is made for each edge in the tree, and one exception is thrown for each leaf node. This puzzle doesn’t have much in the way of a lesson. It does demonstrate that exponential algorithms are impractical for all but the smallest inputs, and it shows that you can write an exponential algorithm without even trying.
103
puzzlers.book Page 143 Thursday, June 9, 2005 10:37 PM
Puzzle 61: The Dating Game
Puzzle 61: The Dating Game The following program exercises some basic features of the Date and Calendar classes. What does it print? import java.util.*; public class DatingGame { public static void main(String[] args) { Calendar cal = Calendar.getInstance(); cal.set(1999, 12, 31); // Year, Month, Day System.out.print(cal.get(Calendar.YEAR) + " "); Date d = cal.getTime(); System.out.println(d.getDay()); } }
143
puzzlers.book Page 144 Thursday, June 9, 2005 10:37 PM
144
Chapter 7 • Library Puzzlers
Solution 61: The Dating Game This program creates a Calendar instance that appears to represent New Year’s Eve, 1999, and prints the year followed by the day. It seems that the program should print 1999 31, but it doesn’t; it prints 2000 1. Could this be the dreaded Y2K problem? No, it’s something much worse: It is the dreaded Date/Calendar problem. When the Java platform was first released, its only support for calendar calculations was the Date class. This class was limited in power, especially when it came to support for internationalization, and it had a basic design flaw: Date instances were mutable. In release 1.1, the Calendar class was added to the platform to rectify the shortcomings of Date; most Date methods were deprecated. Unfortunately, this only made a bad situation worse. Our program illustrates a few of the problems with the Date and Calendar APIs. The program’s first bug is in the method invocation cal.set(1999, 12, 31). When months are represented numerically, convention dictates that the first month be assigned the number 1. Unfortunately, Date represents January as 0, and Calendar perpetuates this mistake. Therefore, this method invocation sets the calendar to the thirty-first day of the thirteenth month of 1999. But the standard (Gregorian) calendar has only 12 months; surely this method invocation should cause an IllegalArgumentException? It should, but it doesn’t. The Calendar class silently substitutes the first month of the next year, in this case, 2000. This explains the first number printed by our program (2000). There are two ways to fix this problem. You could change the second parameter of the cal.set invocation from 12 to 11, but that would be confusing. The number 11 would suggest November to readers. It would be better to use the constant that Calendar declares for this purpose, which is Calendar.DECEMBER. What about the second number printed by the program? The cal.set invocation clearly indicates that the calendar is set to the thirty-first day of the month. The Date instance d represents the same point in time as the Calendar, so its getDay method should return 31, but the program prints 1. What is going on? To find out, you have to read the documentation, which says that Date.getDay returns the day of the week represented by the Date instance, not the day of the month. The returned value is 0-based, starting at Sunday, so the 1 printed by the program indicates that January 31, 2000, fell on a Monday. Note that the corresponding Calendar method, get(Calendar.DAY_OF_WEEK), inexplicably returns a day-of-the-week value that is 1-based, not 0-based like the value returned by its Date counterpart.
puzzlers.book Page 145 Thursday, June 9, 2005 10:37 PM
Puzzle 62: The Name Game
There are also two ways to fix this problem. You could call the confusingly named Date.date method, which returns the day of the month. Like most Date methods, however, it is deprecated, so you would be better off dispensing with Date entirely and calling the Calendar method get(Calendar.DAY_OF_MONTH). With both problems fixed, the program prints 1999 31 as expected: public class DatingGame { public static void main(String[] args) { Calendar cal = Calendar.getInstance(); cal.set(1999, Calendar.DECEMBER, 31); System.out.print(cal.get(Calendar.YEAR) + " "); System.out.println(cal.get(Calendar.DAY_OF_MONTH)); } }
This puzzle only scratches the surface of the defects in Calendar and Date. These APIs are minefields. Other serious problems with Calendar include weak typing (nearly everything is an int), an overly complex state-space, poor structure, inconsistent naming, and inconsistent semantics. Be careful when using Calendar or Date; always consult the API documentation. The lesson for API designers is: If you canâ&#x20AC;&#x2122;t get it right the first time, at least get it right the second; there may not be a third. If your first attempt at an API has serious problems, your customers may be forgiving and give you another chance. If your second attempt has problems, you may be stuck with them for good.
145
CHAPTER 4
Standard JSF Tags
BUY
from informIT and
SAVE 35%
Enter the coupon code JAVAONE10 during checkout.
Google Bookmarks
Delicious
Digg
StumbleUpon
Core JavaServer Faces, Third Edition AUTHORS David Geary and Cay S. Horstmann
FORMATS & ISBNs Book: 9780137012893 eBook: 9780137013937 Safari Books Online: 9780137151585
JavaServer Faces (JSF) is the standard Java EE technology for building web user interfaces. JSF 2.0 is a major upgrade, which not only adds many useful features but also greatly simplifies the programming model by using annotations and “convention over configuration” for common tasks. To help you quickly tap into the power of JSF 2.0, the third edition of Core JavaServer™ Faces has been completely updated to make optimum use of all the new features.
THE BOOK INCLUDES • Three totally new chapters on using Facelets tags for templating, building composite components, and developing Ajax applications • Guidance on building robust applications with minimal hand coding and maximum productivity
Core JavaServer™ Faces, Third Edition, provides everything you need to master the powerful and time-saving features of JSF 2.0 and is the perfect guide for programmers developing Java EE 6 web apps on Glassfish or another Java EE 6-compliant application servers, as well as servlet runners such as Tomcat 6.
• A complete explanation of the basic building blocks • Coverage of advanced tasks • Solutions to a variety of common challenges • Proven solutions, hints, tips, and “how-tos”
informit.com/learnjava
basic-components.fm Page 100 Thursday, May 6, 2010 9:51 AM
STANDARD JSF TAGS
Topics in This Chapter • “An Overview of the JSF Core Tags” on page 102 • “An Overview of the JSF HTML Tags” on page 105 • “Panels” on page 115 • “The Head, Body, and Form Tags” on page 118 • “Text Fields and Text Areas” on page 123 • “Buttons and Links” on page 134 • “Selection Tags” on page 145 • “Messages” on page 171
basic-components.fm Page 101 Thursday, May 6, 2010 9:51 AM
4
Chapter
Development of compelling JSF applications requires a good grasp of the JSF tag libraries. JSF 1.2 had two tag libraries: core and HTML. As of JSF 2.0, there are six libraries with over 100 tags—see Table 4–1. In this chapter, we cover the core library and most of the HTML library. One HTML library component—the data table—is so complex that it is covered separately in Chapter 6. Table 4–1
JSF Tag Libraries Namespace Identifier
Commonly Used Prefix
Number of Tags
See Chapter
Core
http://java.sun.com/ jsf/core
f:
27
See Table 4–2
HTML
http://java.sun.com/ jsf/html
h:
31
4 and 6
Facelets
http://java.sun.com/ jsf/facelets
ui:
11
5
Composite Components
http://java.sun.com/ jsf/composite
composite:
12
9
JSTL Core
http://java.sun.com/ jsp/jstl/core
c:
7
13
JSTL Functions
http://java.sun.com/ jsp/jstl/functions
fn:
16
2
Library
101
basic-components.fm Page 102 Thursday, May 6, 2010 9:51 AM
102
Chapter 4 ■ Standard JSF Tags
An Overview of the JSF Core Tags The core library contains the tags that are independent of HTML rendering. The core tags are listed in Table 4–2. Table 4–2
JSF Core Tags See Chapter
Tag
Description
attribute
Sets an attribute (key/value) in its parent component.
4
param
Adds a parameter child component to its parent component.
4
facet
Adds a facet to a component.
4
actionListener
Adds an action listener to a component.
8
setPropertyActionListener
Adds an action listener that sets a property.
8
valueChangeListener
Adds a value change listener to a component.
8
phaseListener
Adds a phase listener to the parent view.
8
event
Adds a component system event listener.
8
converter
Adds an arbitrary converter to a component.
7
convertDateTime
Adds a datetime converter to a component.
7
convertNumber
Adds a number converter to a component.
7
validator
Adds a validator to a component.
7
validateDoubleRange
Validates a double range for a component’s value.
7
validateLength
Validates the length of a component’s value.
7
validateLongRange
Validates a long range for a component’s value.
7
validateRequired
Checks that a value is present.
7
validateRegex
Validates a value against a regular expression.
7
basic-components.fm Page 103 Thursday, May 6, 2010 9:51 AM
An Overview of the JSF Core Tags
Table 4–2
JSF Core Tags (cont.) See Chapter
Tag
Description
validateBean
Uses the Bean Validation API (JSR 303) for validation.
7
loadBundle
Loads a resource bundle, stores properties as a Map.
2
selectitems
Specifies items for a select one or select many component.
4
selectitem
Specifies an item for a select one or select many component.
4
verbatim
Turns text containing markup into a component.
4
viewParam
Defines a “view parameter” that can be initialized with a request parameter.
3
metadata
Holds view parameters. May hold other metadata in the future.
3
ajax
Enables Ajax behavior for components.
11
view
Use for specifying the page locale or a phase listener.
2 and 7
subview
Not needed with facelets.
Most of the core tags represent objects you add to components, such as the following: •
Attributes
•
Parameters
•
Facets
•
Listeners
•
Converters
•
Validators
•
Selection items
All of the core tags are discussed at length in different places in this book, as shown in Table 4–1.
103
basic-components.fm Page 104 Thursday, May 6, 2010 9:51 AM
104
Chapter 4 ■ Standard JSF Tags
Attributes, Parameters, and Facets The f:attribute, f:param, and f:facet tags are general-purpose tags to add information to a component. Any component can store arbitrary name/value pairs in its attribute map. You can set an attribute in a page and later retrieve it programatically. For example, in “Supplying Attributes to Converters” on page 289 of Chapter 7, we set the separator character for credit card digit groups like this: <h:outputText value="#{payment.card}"> <f:attribute name="separator" value="-" /> </h:outputText>
The converter that formats the output retrieves the attribute from the component. The f:param tag also lets you define a name/value pair, but the value is placed in a separate child component, a much bulkier storage mechanism. However, the child components form a list, not a map. You use f:param if you need to supply a number of values with the same name (or no name at all). You saw an example in “Messages with Variable Parts” on page 42 of Chapter 2, where the h:outputFormat component contains a list of f:param children. NOTE: the h:commandlink component turns its f:param children into HTTP request name/value pairs. The event listener that is activated when the user clicks the link can then retrieve the name/value pairs from the request map. We demonstrate this technique in Chapter 8.
Finally, f:facet adds a named component to a component’s facet map. A facet is not a child component; each component has both a list of child components and a map of named facet components. The facet components are usually rendered in a special place. The root of a Facelets page has two facets named "head" and "body". You will see in “Headers, Footers, and Captions” on page 212 of Chapter 6 how to use facets named "header" and "footer" in data tables. Table 4–3 shows the attributes for the f:attribute, f:param, and f:facet tags. Table 4–3
Attributes for f:attribute, f:param, and f:facet
Attribute
Description
name
The attribute, parameter component, or facet name
value
The attribute or parameter component value (does not apply to f:facet)
binding, id
See Table 4–5 on page 107 (f:param only)
basic-components.fm Page 105 Thursday, May 6, 2010 9:51 AM
An Overview of the JSF HTML Tags
NOTE: All tag attributes in this chapter, except for var and id, accept value or method expressions. The var attribute must be a string. The id attribute can be a string or an immediate ${...} expression.
An Overview of the JSF HTML Tags Table 4–4 lists all HTML tags. We can group these tags in the following categories: •
Inputs (input...)
•
Outputs (output..., graphicImage)
•
Commands (commandButton and commandLink)
•
GET Requests (button, link, outputLink)
•
Selections (checkbox, listbox, menu, radio)
•
HTML pages (head, body, form, outputStylesheet, outputScript)
•
Layouts (panelGrid, panelGroup)
•
Data table (dataTable and column); see Chapter 6
•
Errors and messages (message, messages)
The JSF HTML tags share common attributes, HTML pass-through attributes, and attributes that support dynamic HTML. Table 4–4
JSF HTML Tags
Tag
Description
head
Renders the head of the page
body
Renders the body of the page
form
Renders a HTML form
outputStylesheet
Adds a stylesheet to the page
outputScript
Adds a script to the page
inputText
Single-line text input control
inputTextarea
Multiline text input control
inputSecret
Password input control
inputHidden
Hidden field
105
basic-components.fm Page 106 Thursday, May 6, 2010 9:51 AM
106
Chapter 4 â&#x2013; Standard JSF Tags
Table 4â&#x20AC;&#x201C;4
JSF HTML Tags (cont.)
Tag
Description
outputLabel
Label for another component for accessibility
outputLink
Link to another web site
outputFormat
Like outputText, but formats compound messages
outputText
Single-line text output
commandButton
Button: submit, reset, or pushbutton
commandLink
Link that acts like a pushbutton
button
Button for issuing a GET request
link
Link for issuing a GET request
message
Displays the most recent message for a component
messages
Displays all messages
graphicImage
Displays an image
selectOneListbox
Single-select listbox
selectOneMenu
Single-select menu
selectOneRadio
Set of radio buttons
selectBooleanCheckbox
Checkbox
selectManyCheckbox
Set of checkboxes
selectManyListbox
Multiselect listbox
selectManyMenu
Multiselect menu
panelGrid
Tabular layout
panelGroup
Two or more components that are laid out as one
dataTable
A feature-rich table control (see Chapter 6)
column
Column in a dataTable (see Chapter 6)
basic-components.fm Page 107 Thursday, May 6, 2010 9:51 AM
An Overview of the JSF HTML Tags
NOTE: The HTML tags may seem overly verbose—for example, selectManyListbox could be more efficiently expressed as multiList. But those verbose names correspond to a component/renderer combination, so selectManyListbox represents a selectMany component paired with a listbox renderer. Knowing the type of component a tag represents is crucial if you want to access components programmatically.
Common Attributes Three types of tag attributes are shared among multiple HTML component tags: •
Basic
•
HTML 4.0
•
DHTML events
Next, we look at each type. Basic Attributes As you can see from Table 4–5, basic attributes are shared by the majority of JSF HTML tags. Table 4–5
Basic HTML Tag Attributesa
Attribute
Component Types
Description
id
A (31)
Identifier for a component
binding
A (31)
Links this component with a backing bean property
rendered
A (31)
A Boolean; false suppresses rendering
value
I, O, C (21)
A component’s value, typically a value expression
valueChangeListener
I (11)
A method expression to a method that responds to value changes
converter
I, O (15)
Converter class name
validator
I (11)
Class name of a validator that is created and attached to a component
107
basic-components.fm Page 108 Thursday, May 6, 2010 9:51 AM
108
Chapter 4 ■ Standard JSF Tags
Table 4–5
Basic HTML Tag Attributesa (cont.)
Attribute
Component Types
required
I (11)
A Boolean; if true, requires a value to be entered in the associated field
converterMessage, validatorMessage, requiredMessage
I (11)
A custom message to be displayed when a conversion or validation error occurs, or when required input is missing
Description
a. A = all, I = input, O = output, C = commands, (n) = number of tags with attribute
All components can have id, binding, and rendered attributes, which we discuss in the following sections. The value and converter attributes let you specify a component value and a means to convert it from a string to an object, or vice versa. The validator, required, and valueChangeListener attributes are available for input components so that you can validate values and react to changes to those values. See Chapter 7 for more information about validators and converters. IDs and Bindings The versatile id attribute lets you do the following: •
Access JSF components from other JSF tags
•
Obtain component references in Java code
•
Access HTML elements with scripts
In this section, we discuss the first two tasks listed above. See “Form Elements and JavaScript” on page 120 for more about the last task. The id attribute lets page authors reference a component from another tag. For example, an error message for a component can be displayed like this: <h:inputText id="name" .../> <h:message for="name"/>
You can also use component identifiers to get a component reference in your Java code. For example, you could access the name component in a listener like this: UIComponent component = event.getComponent().findComponent("name");
The preceding call to findComponent has a caveat: The component that generated the event and the name component must be in the same form. There is another way to
basic-components.fm Page 109 Thursday, May 6, 2010 9:51 AM
An Overview of the JSF HTML Tags
access a component in your Java code. Define the component as an instance field of a class. Provide property getters and setters for the component. Then use the binding attribute, which you specify in a JSF page, like this: <h:inputText binding="#{form.nameField}" .../>
The binding attribute is specified with a value expression. That expression refers to a read-write bean property, such as this one: private UIComponent nameField = new UIInput(); public UIComponent getNameField() { return nameField; } public void setNameField(UIComponent newValue) { nameField = newValue; }
See “Backing Beans” on page 38 of Chapter 2 for more information about the binding attribute. The JSF implementation sets the property to the component, so you can programatically manipulate components. Values, Converters, and Validators Inputs, outputs, commands, and data tables all have values. Associated tags in the HTML library, such as h:inputText and h:dataTable, come with a value attribute. You can specify values with a string, like this: <h:commandButton value="Logout" .../>
Most of the time you will use a value expression—for example: <h:inputText value="#{customer.name}"/>
The converter attribute, shared by inputs and outputs, lets you attach a converter to a component. Input tags also have a validator attribute that you can use to attach a validator to a component. Converters and validators are discussed at length in Chapter 7. Conditional Rendering You use the rendered attribute to include or exclude a component, depending on a condition. For example, you may want to render a “Logout” button only if the user is currently logged in: <h:commandButton ... rendered="#{user.loggedIn}"/>
To conditionally include a group of components, include them in an h:panelGrid with a rendered attribute. See “Panels” on page 115 for more information.
109
basic-components.fm Page 110 Thursday, May 6, 2010 9:51 AM
110
Chapter 4 ■ Standard JSF Tags
TIP: Remember, you can use operators in value expressions. For example, you might have a view that acts as a tabbed pane by optionally rendering a panel depending on the selected tab. In that case, you could use h:panelGrid like this: <h:panelGrid rendered="#{bean.selectedTab == 'Movies'}"/> The preceding code renders the movies panel when the user selects the Movies tab.
NOTE: Sometimes, you will see the JSTL c:if construct used for conditional rendering. However, that is less efficient than the rendered attribute.
HTML 4.0 Attributes JSF HTML tags have appropriate HTML 4.0 pass-through attributes. Those attribute values are passed through to the generated HTML element. For example, <h:inputText value="#{form.name.last}" size="25".../> generates this HTML: <input type="text" size="25".../>. Notice that the size attribute is passed through to HTML. The HTML 4.0 attributes are listed in Table 4–6. Table 4–6
HTML 4.0 Pass-Through Attributesa
Attribute
Description
accesskey (16)
A key, typically combined with a system-defined metakey, that gives focus to an element.
accept (1)
Comma-separated list of content types for a form.
acceptcharset (1)
Comma- or space-separated list of character encodings for a form. The HTML accept-charset attribute is specified with the JSF attribute named acceptcharset.
alt (5)
Alternative text for nontextual elements such as images or applets.
border (4)
Pixel value for an element’s border width.
charset (3)
Character encoding for a linked resource.
coords (3)
Coordinates for an element whose shape is a rectangle, circle, or polygon.
dir (26)
Direction for text. Valid values are "ltr" (left to right) and "rtl" (right to left).
basic-components.fm Page 111 Thursday, May 6, 2010 9:51 AM
An Overview of the JSF HTML Tags
Table 4–6
HTML 4.0 Pass-Through Attributesa (cont.)
Attribute
Description
disabled (14)
Disabled state of an input element or button.
hreflang (3)
Base language of a resource specified with the href attribute; hreflang may only be used with href.
lang (26)
Base language of an element’s attributes and text.
maxlength (2)
Maximum number of characters for text fields.
readonly (11)
Read-only state of an input field; text can be selected in a read-only field but not edited.
rel (3)
Relationship between the current document and a link specified with the href attribute.
rev (3)
Reverse link from the anchor specified with href to the current document. The value of the attribute is a space-separated list of link types.
rows (1)
Number of visible rows in a text area. h:dataTable has a rows attribute, but it is not an HTML pass-through attribute.
shape (3)
Shape of a region. Valid values: default, rect, circle, poly (default signifies the entire region).
size (4)
Size of an input field.
style (26)
Inline style information.
styleClass (26)
Style class; rendered as HTML class attribute.
tabindex (16)
Numerical value specifying a tab index.
target (5)
The name of a frame in which a document is opened.
title (25)
A title, used for accessibility, that describes an element. Visual browsers typically create tooltips for the title’s value.
type (4)
Type of a link—for example, "stylesheet".
width (3)
Width of an element.
a. (n) = number of tags with attribute
The attributes listed in Table 4–6 are defined in the HTML specification, which you can access online at http://www.w3.org/TR/REC-html40. That web site is an excellent resource for deep digging into HTML.
111
basic-components.fm Page 112 Thursday, May 6, 2010 9:51 AM
112
Chapter 4 ■ Standard JSF Tags
Styles You can use CSS styles, either inline (style) or classes (styleClass), to influence how components are rendered: <h:outputText value="#{customer.name}" styleClass="emphasis"/> <h:outputText value="#{customer.id}" style="border: thin solid blue"/>
CSS style attributes can be value expressions—that gives you programmatic control over styles. Resources You can include a stylesheet in the usual way, with an HTML link tag. But that is tedious if your pages are at varying directory nesting levels—you would always need to update the stylesheet directory when you move a page. More importantly, if you assemble pages from different pieces—as described in Chapter 5—you don’t even know where your pieces end up. Since JSF 2.0, there is a better way. You can place stylesheets, JavaScript files, images, and other files into a resources directory in the root of your web application. Subdirectories of this directory are called libraries. You can create any libraries that you like. In this book, we often use libraries css, images, and javascript. To include a stylesheet, use the tag: <h:outputStylesheet library="css" name="styles.css"/>
The tag adds a link of the form <link href="/context-root/faces/javax.faces.resource/styles.css?ln=css" rel="stylesheet" type="text/css"/>
to the header of the page. To include a script resource, use the outputScript tag instead: <h:outputScript name="jsf.js" library="javascript" target="head" />
If the target attribute is head or body, the script is appended to the "head" or "body" facet of the root component, which means that it appears at the end of the head or body in the generated HTML. If there is no target element, the script is inserted in the current location. To include an image from a library, you use the graphicImage tag: <h:graphicImage name="logo.png" library="images"/>
There is a versioning mechanism for resource libraries and individual resources. You can add subdirectories to the library directory and place newer versions of
basic-components.fm Page 113 Thursday, May 6, 2010 9:51 AM
An Overview of the JSF HTML Tags
files into them. The subdirectory names are simply the version numbers. For example, suppose you have the following directories: resources/css/1_0_2 resources/css/1_1
Then the latest version (resources/css/1_1) will be used. Note that you can add new versions of a library in a running application. Similarly, you can add new versions of an individual resource, but the naming scheme is a bit odd. You replace the resource with a directory of the same name, then use the version name as the file name. You can add an extension if you like. For example: resources/css/styles.css/1_0_2.css resources/css/styles.css/1_1.css
The version numbers must consist of decimal numbers, separated by underscores. They are compared in the usual way, first comparing the major version numbers and using the minor numbers to break ties. There is also a mechanism for supplying localized versions of resources. Unfortunately, that mechanism is unintuitive and not very useful. Localized resources have a prefix, such as resources/de_DE/images, but the prefix is not treated in the same way as a bundle suffix. There is no fallback mechanism. That is, if an image is not found in resources/de_DE/images, then resources/de/images and resources/images are not consulted. Moreover, the locale prefix is not simply the current locale. Instead, it is obtained by a curious lookup, which you enable by following these steps: 1.
Add the line <message-bundle>name of a resource bundle used in your application</message-bundle>
inside the application element of faces-config.xml 2.
Inside each localized version of that resource bundle, place a name/value pair javax.faces.resource.localePrefix=prefix
3.
Place the matching resources into resources/prefix/library/...
For example, if you use the message bundle com.corejsf.messages, and the file com.corejsf.messages_de contains the entry javax.faces.resource.localePrefix=german
then you place the German resources into resources/german. (The prefix need not use the standard language and country codes, and in fact it is a good idea not to use them so that you donâ&#x20AC;&#x2122;t raise false hopes.)
113
basic-components.fm Page 114 Thursday, May 6, 2010 9:51 AM
114
Chapter 4 ■ Standard JSF Tags
CAUTION: Unfortunately, this localization scheme is unappealing in practice. Once you define a locale prefix, that prefix is used for all resources. Suppose you wanted to have different images for the German and English versions of your site. Then you would also have to duplicate every other resource. Hopefully, this will be fixed in a future version of JSF.
DHTML Events Client-side scripting is useful for all sorts of tasks, such as syntax validation or rollover images, and it is easy to use with JSF. HTML attributes that support scripting, such as onclick and onchange are called dynamic HTML (DHTML) event attributes. JSF supports DHTML event attributes for nearly all of the JSF HTML tags. Those attributes are listed in Table 4–7. Table 4–7
DHTML Event Attributesa
Attribute
Description
onblur (16)
Element loses focus
onchange (11)
Element’s value changes
onclick (17)
Mouse button is clicked over the element
ondblclick (21)
Mouse button is double-clicked over the element
onfocus (16)
Element receives focus
onkeydown (21)
Key is pressed
onkeypress (21)
Key is pressed and subsequently released
onkeyup (21)
Key is released
onload (1)
Page is loaded
onmousedown (21)
Mouse button is pressed over the element
onmousemove (21)
Mouse moves over the element
onmouseout (21)
Mouse leaves the element’s area
onmouseover (21)
Mouse moves onto an element
onmouseup (21)
Mouse button is released
onreset (1)
Form is reset
basic-components.fm Page 115 Thursday, May 6, 2010 9:51 AM
Panels
Table 4–7
DHTML Event Attributesa (cont.)
Attribute
Description
onselect (11)
Text is selected in an input field
onsubmit (1)
Form is submitted
onunload (1)
Page is unloaded
a. (n) = number of tags with attribute
The DHTML event attributes listed in Table 4–7 let you associate client-side scripts with events. Typically, JavaScript is used as a scripting language, but you can use any scripting language you like. See the HTML specification for more details. TIP: You will probably add client-side scripts to your JSF pages soon after you start using JSF. One common use is to submit a request when an input’s value is changed so that value change listeners are immediately notified of the change, like this: <h:selectOneMenu onchange="submit()"...>
Panels Up to this point, we have used HTML tables to lay out components. Creating table markup by hand is tedious, so now we’ll look at alleviating some of that tedium with h:panelGrid, which generates the HTML markup for laying out components in rows and columns. NOTE: The h:panelGrid tag uses HTML tables for layout, which some web designers find objectionable. You can certainly use CSS layout instead of h:panelGrid. A future version of h:panelGrid may have an option for using CSS layout as well.
You can specify the number of columns with the columns attribute, like this: <h:panelGrid columns="3"> ... </h:panelGrid>
The columns attribute is not mandatory—if you do not specify it, the number of columns defaults to 1. The h:panelGrid tag places components in columns from left to right and top to bottom. For example, if you have a panel grid with three
115
basic-components.fm Page 116 Thursday, May 6, 2010 9:51 AM
116
Chapter 4 ■ Standard JSF Tags
columns and nine components, you will wind up with three rows, each containing three columns. If you specify three columns and 10 components, you will have four rows, and in the last row only the first column will contain the tenth component. Table 4–8 lists h:panelGrid attributes. Table 4–8
Attributes for h:panelGrid
Attributes
Description
bgcolor
Background color for the table
border
Width of the table’s border
cellpadding
Padding around table cells
cellspacing
Spacing between table cells
columnClasses
Comma-separated list of CSS classes for columns
columns
Number of columns in the table
footerClass
CSS class for the table footer
frame
Specification for sides of the frame surrounding the table that are to be drawn; valid values: none, above, below, hsides, vsides, lhs, rhs, box, border
headerClass
CSS class for the table header
rowClasses
Comma-separated list of CSS classes for rows
rules
Specification for lines drawn between cells; valid values: groups, rows, columns, all
summary
Summary of the table’s purpose and structure used for nonvisual feedback, such as speech
captionClass
,
captionStyle
CSS class or style for the caption; a panel caption is optionally supplied by a facet named "caption"
binding, id, rendered, value
Basic attributesa
dir, lang, style, styleClass, title, width
HTML 4.0b
basic-components.fm Page 117 Thursday, May 6, 2010 9:51 AM
Panels
Table 4–8
Attributes for h:panelGrid (cont.)
Attributes
Description
onclick, ondblclick, onkeydown, onkeypress, onkeyup, onmousedown, onmousemove, onmouseout, onmouseover, onmouseup
DHTML eventsc
a. See Table 4–5 on page 107 for information about basic attributes. b. See Table 4–6 on page 110 for information about HTML 4.0 attributes. c. See Table 4–7 on page 114 for information about DHTML event attributes.
You can specify CSS classes for different parts of the table: header, footer, rows, and columns. The columnClasses and rowClasses specify lists of CSS classes that are applied to columns and rows, respectively. If those lists contain fewer class names than rows or columns, the CSS classes are reused. That makes it possible to specify classes, like this: rowClasses="evenRows, oddRows"
and columnClasses="evenColumns, oddColumns"
The cellpadding, cellspacing, frame, rules, and summary attributes are HTML passthrough attributes that apply only to tables. See the HTML 4.0 specification for more information. h:panelGrid is often used with h:panelGroup, which groups two or more components
so they are treated as one. For example, you might group an input field and its error message, like this: <h:panelGrid columns="2"> ... <h:panelGroup> <h:inputText id="name" value="#{user.name}"> <h:message for="name"/> </h:panelGroup> ... </h:panelGrid>
Grouping the text field and error message puts them in the same table cell. (We discuss the h:message tag in the section “Messages” on page 171.) h:panelGroup is a simple tag with only a handful of attributes. Those attributes are listed in Table 4–9.
117
basic-components.fm Page 118 Thursday, May 6, 2010 9:51 AM
118
Chapter 4 ■ Standard JSF Tags
Table 4–9
Attributes for h:panelGroup
Attributes
Description
layout
If the value is "block", use an HTML div to lay out the children; otherwise, use a span
binding, id, rendered
Basic attributesa
style, styleClass
HTML 4.0b
a. See Table 4–5 on page 107 for information about basic attributes. b. See Table 4–6 on page 110 for information about HTML 4.0 attributes.
The Head, Body, and Form Tags Table 4–10 shows the attributes of the h:head and h:body tags. All of them are basic or HTML/DHTML attributes. Table 4–10
Attributes for h:head and h:body
Attributes
Description
id, binding, rendered
Basic attributesa
dir, lang h:body only: style, styleClass, target, title
HTML 4.0b attributes
h:body only: onclick, ondblclick, onkeydown, onkeypress, onkeyup, onload, onmousedown, onmousemove, onmouseout, onmouseover, onmouseup, onunload
DHTML eventsc
a. See Table 4–5 on page 107 for information about basic attributes. b. See Table 4–6 on page 110 for information about HTML 4.0 attributes. c. See Table 4–7 on page 114 for information about DHTML event attributes.
Web applications run on form submissions, and JSF applications are no exception. Table 4–11 lists all h:form attributes.
basic-components.fm Page 119 Thursday, May 6, 2010 9:51 AM
The Head, Body, and Form Tags
Table 4–11
Attributes for h:form
Attributes
Description
prependId
true (default) if the ID of this form is prepended to the IDs of its components; false to suppress prepending the form ID (useful if the ID is used in JavaScript code)
binding, id, rendered
Basic attributesa
accept, acceptcharset, dir, enctype, lang, style, styleClass, target, title
HTML 4.0b attributes
onclick, ondblclick, onfocus, onkeydown, onkeypress, onkeyup, onmousedown, onmousemove, onmouseout, onmouseover, onmouseup, onreset, onsubmit
DHTML eventsc
a. See Table 4–5 on page 107 for information about basic attributes. b. See Table 4–6 on page 110 for information about HTML 4.0 attributes. c. See Table 4–7 on page 114 for information about DHTML event attributes.
Although the HTML form tag has method and action attributes, h:form does not. Because you can save state in the client—an option that is implemented as a hidden field—posting forms with the GET method is disallowed. The contents of that hidden field can be quite large and may overrun the buffer for request parameters, so all JSF form submissions are implemented with the POST method. There is no need for an anchor attribute since JSF form submissions always post to the current page. (Navigation to a new page happens after the form data have been posted.) The h:form tag generates an HTML form element. For example, if, in a JSF page named /index.xhtml, you use an h:form tag with no attributes, the Form renderer generates HTML like this: <form id="_id0" method="post" action="/faces/index.xhtml" enctype="application/x-www-form-urlencoded">
If you do not specify the id attribute explicitly, a value is generated by the JSF implementation, as is the case for all generated HTML elements. You can explicitly specify the id attribute for forms so that it can be referenced in stylesheets or scripts.
119
basic-components.fm Page 120 Thursday, May 6, 2010 9:51 AM
120
Chapter 4 ■ Standard JSF Tags
Form Elements and JavaScript JavaServer Faces is all about server-side components, but it is also designed to work with scripting languages, such as JavaScript. For example, the application shown in Figure 4–1 uses JavaScript to confirm that a password field matches a password confirm field. If the fields do not match, a JavaScript dialog is displayed. If they do match, the form is submitted.
Figure 4–1
Using JavaScript to access form elements
We use the id attribute to assign names to the relevant HTML elements so that we can access them with JavaScript: <h:form> ... <h:inputSecret id="password" .../> <h:inputSecret id="passwordConfirm" .../> ... <h:commandButton type="button" onclick="checkPassword(this.form)"/> ... </h:form>
When the user clicks the button, a JavaScript function checkPassword is invoked. Here is the implementation of the function: function checkPassword(form) { var password = form[form.id + ":password"].value; var passwordConfirm = form[form.id + ":passwordConfirm"].value; if (password == passwordConfirm) form.submit();
basic-components.fm Page 121 Thursday, May 6, 2010 9:51 AM
The Head, Body, and Form Tags
else alert("Password and password confirm fields don't match"); }
To understand the syntax used to access form elements, look at the HTML produced by the preceding code: <form id="_id0" method="post" action="/javascript/faces/index.xhtml" enctype="application/x-www-form-urlencoded"> ... <input id="_id0:password" type="text" name="registerForm:password"/> ... <input type="button" name="_id0:_id5" value="Submit Form" onclick="checkPassword(this.form)"/> ... </form>
All form controls generated by JSF have names that conform to formName:componentName
where formName represents the name of the control’s form and componentName represents the control’s name. If you do not specify id attributes, the JSF implementation creates identifiers for you. In our case, we didn’t specify an id for the form. Therefore, to access the password field in the preceding example, the script uses the expression: form[form.id + ":password"] NOTE: The ID values generated by the JSF implementation seem to get more complex with every version of JSF. In the past, they were fairly straightforward (such as _id0), but more recent versions use IDs such as j_id2059540600_7ac21823. For greater clarity, we use the simpler IDs in our examples.
The directory structure for the application shown in Figure 4–1 is shown in Figure 4–2. The JSF page is listed in Listing 4–1. The JavaScript code, stylesheets, and resource bundle are listed in Listings 4–2 through 4–4.
121
basic-components.fm Page 122 Thursday, May 6, 2010 9:51 AM
122
Chapter 4 ■ Standard JSF Tags
Figure 4–2
The JavaScript example directory structure
Listing 4–1
javascript/web/index.xhtml
1. <?xml version="1.0" encoding="UTF-8"?> 2. <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" 3. "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> 4. <html xmlns="http://www.w3.org/1999/xhtml" 5. xmlns:h="http://java.sun.com/jsf/html"> 6. <h:head> 7. <title>#{msgs.windowTitle}</title> 8. <h:outputStylesheet library="css" name="styles.css"/> 9. <h:outputScript library="javascript" name="checkPassword.js"/> 10. </h:head> 11. <h:body> 12. <h:form> 13. <h:panelGrid columns="2" columnClasses="evenColumns, oddColumns"> 14. #{msgs.namePrompt} 15. <h:inputText/> 16. #{msgs.passwordPrompt} 17. <h:inputSecret id="password"/> 18. #{msgs.confirmPasswordPrompt} 19. <h:inputSecret id="passwordConfirm"/> 20. </h:panelGrid> 21. <h:commandButton type="button" value="Submit Form" 22. onclick="checkPassword(this.form)"/> 23. </h:form> 24. </h:body> 25. </html>
basic-components.fm Page 123 Thursday, May 6, 2010 9:51 AM
Text Fields and Text Areas
Listing 4–2
javascript/web/resources/javascript/checkPassword.js
1. function checkPassword(form) { 2. var password = form[form.id + ":password"].value; 3. var passwordConfirm = form[form.id + ":passwordConfirm"].value; 4. if (password == passwordConfirm) 5. 6. form.submit(); 7. else 8. alert("Password and password confirm fields don't match"); 9. }
Listing 4–3
javascript/web/resources/css/styles.css
1. .evenColumns { 2. font-style: italic; 3. } 4. 5. .oddColumns { 6. padding-left: 1em; 7. }
Listing 4–4
javascript/src/java/com/corejsf/messages.properties
1. windowTitle=Accessing Form Elements with JavaScript 2. namePrompt=Name: 3. passwordPrompt=Password: 4. confirmPasswordPrompt=Confirm Password:
Text Fields and Text Areas Text inputs are the mainstay of most web applications. JSF supports three varieties represented by the following tags: • • •
h:inputText h:inputSecret h:inputTextarea
Since the three tags use similar attributes, Table 4–12 lists attributes for all three.
123
basic-components.fm Page 124 Thursday, May 6, 2010 9:51 AM
124
Chapter 4 ■ Standard JSF Tags
Table 4–12 Attributes for h:inputText, h:inputSecret, h:inputTextarea, and h:inputHidden Attributes
Description
cols
For h:inputTextarea only—number of columns.
immediate
Process validation early in the life cycle.
redisplay
For h:inputSecret only—when true, the input field’s value is redisplayed when the web page is reloaded.
required
Require input in the component when the form is submitted.
rows
For h:inputTextarea only—number of rows.
valueChangeListener
A specified listener that is notified of value changes.
label
A description of the component for use in error messages. Does not apply to h:inputHidden.
binding, converter, converterMessage id, rendered, required, requiredMessage value, validator, validatorMessage
Basic attributes.a , ,
accesskey, alt, dir, disabled, lang, maxlength, readonly, size, style, styleClass, tabindex, title
HTML 4.0 pass-through attributesb— alt, maxlength, and size do not apply to h:inputTextarea. None apply to h:inputHidden.
autocomplete
If the value is "off", render the nonstandard HTML attribute autocomplete="off" (h:inputText and h:inputSecret only).
onblur, onchange, onclick, ondblclick, onfocus, onkeydown, onkeypress, onkeyup, onmousedown, onmousemove, onmouseout, onmouseover, onmouseup, onselect
DHTML events. None apply to h:inputHidden.c
a. See Table 4–5 on page 107 for information about basic attributes. b. See Table 4–6 on page 110 for information about HTML 4.0 attributes. c. See Table 4–7 on page 114 for information about DHTML event attributes.
basic-components.fm Page 125 Thursday, May 6, 2010 9:51 AM
Text Fields and Text Areas
All three tags have immediate, required, value, and valueChangeListener attributes. The immediate attribute is used primarily for value changes that affect the user interface and is rarely used by these three tags. Instead, it is more commonly used by other input components such as menus and listboxes. See “Immediate Components” on page 320 of Chapter 8 for more information about the immediate attribute. Three attributes in Table 4–12 are each applicable to only one tag: cols, rows, and redisplay. The rows and cols attributes are used with h:inputTextarea to specify the number of rows and columns, respectively, for the text area. The redisplay attribute, used with h:inputSecret, is a boolean that determines whether a secret field retains its value—and therefore redisplays it—when the field’s form is resubmitted. Table 4–13 shows sample uses of the h:inputText and h:inputSecret tags. Table 4–13
h:inputText and h:inputSecret Examples
Example
Result
<h:inputText value="#{form.testString}" readonly="true"/> <h:inputSecret value="#{form.passwd}" redisplay="true"/> <h:inputSecret value="#{form.passwd}" redisplay="false"/>
(shown after an unsuccessful form submit)
(shown after an unsuccessful form submit)
<h:inputText value="inputText" style="color: Yellow; background: Teal;"/>
<h:inputText value="1234567" size="5"/>
<h:inputText value="1234567890" maxlength="6" size="10"/>
The first example in Table 4–13 produces the following HTML: <input type="text" name="_id0:_id4" value="12345678901234567890" readonly="readonly"/>
125
basic-components.fm Page 126 Thursday, May 6, 2010 9:51 AM
126
Chapter 4 ■ Standard JSF Tags
The input field is read-only, so our form bean defines only a getter method: private String testString = "12345678901234567890"; public String getTestString() { return testString; }
The h:inputSecret examples illustrate the use of the redisplay attribute. If that attribute is true, the text field stores its value between requests and, therefore, the value is redisplayed when the page reloads. If redisplay is false, the value is discarded and is not redisplayed. The size attribute specifies the number of visible characters in a text field. But because most fonts are variable width, the size attribute is not precise, as you can see from the fifth example in Table 4–13, which specifies a size of 5 but displays six characters. The maxlength attribute specifies the maximum number of characters a text field will display. That attribute is precise. Both size and maxlength are HTML pass-through attributes. Table 4–14 shows examples of the h:inputTextarea tag. The h:inputTextarea has cols and rows attributes to specify the number of columns and rows, respectively, in the text area. The cols attribute is analogous to the size attribute for h:inputText and is also imprecise. Table 4–14
h:inputTextarea Examples
Example <h:inputTextarea rows="5"/>
<h:inputTextarea cols="5"/>
<h:inputTextarea value="123456789012345" rows="3" cols="10"/>
<h:inputTextarea value="#{form.dataInRows}" rows="2" cols="15"/>
Result
basic-components.fm Page 127 Thursday, May 6, 2010 9:51 AM
Text Fields and Text Areas
If you specify one long string for h:inputTextarea’s value, the string will be placed in its entirety in one line, as you can see from the third example in Table 4–14. If you want to put data on separate lines, you can insert newline characters (\n) to force a line break. For example, the last example in Table 4–14 accesses the dataInRows property of a backing bean. That property is implemented like this: private String dataInRows = "line one\nline two\nline three"; public void setDataInRows(String newValue) { dataInRows = newValue; } public String getDataInRows() { return dataInRows; }
Hidden Fields JSF provides support for hidden fields with h:inputHidden. Hidden fields are often used with JavaScript actions to send data back to the server. The h:inputHidden tag has the same attributes as the other input tags, except that it does not support the standard HTML and DHTML tags.
Using Text Fields and Text Areas Next, we take a look at a complete example that uses text fields and text areas. The application shown in Figure 4–3 uses h:inputText, h:inputSecret, and h:inputTextarea to collect personal information from a user. The values of those components are wired to bean properties, which are accessed in the thankYou.xhtml page that redisplays the information the user entered. Three things are noteworthy about the following application. First, the JSF pages reference a user bean (com.corejsf.UserBean). Second, the h:inputTextarea tag transfers the text entered in a text area to the model (in this case, the user bean) as one string with embedded newlines (\n). We display that string by using the HTML <pre> element to preserve that formatting. Third, for illustration, we use the style attribute to format output. A more industrial-strength application would presumably use stylesheets exclusively to make global style changes easier to manage.
127
basic-components.fm Page 128 Thursday, May 6, 2010 9:51 AM
128
Chapter 4 ■ Standard JSF Tags
Figure 4–3
Using text fields and text areas
Figure 4–4 shows the directory structure for the application shown in Figure 4–3. Listings 4–5 through 4–8 show the pertinent JSF pages, managed beans, faces configuration file, and resource bundle.
Figure 4–4
Directory structure of the text fields and text areas example
basic-components.fm Page 129 Thursday, May 6, 2010 9:51 AM
Text Fields and Text Areas
Listing 4–5
personalData/web/index.xhtml
1. <?xml version="1.0" encoding="UTF-8"?> 2. <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" 3. "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> 4. <html xmlns="http://www.w3.org/1999/xhtml" 5. xmlns:h="http://java.sun.com/jsf/html"> 6. <h:head> 7. <title>#{msgs.indexWindowTitle}</title> 8. </h:head> 9. <h:body> 10. <h:outputText value="#{msgs.indexPageTitle}" 11. style="font-style: italic; font-size: 1.5em"/> 12. <h:form> 13. <h:panelGrid columns="2"> 14. #{msgs.namePrompt} 15. <h:inputText value="#{user.name}"/> 16. #{msgs.passwordPrompt} 17. <h:inputSecret value="#{user.password}"/> 18. #{msgs.tellUsPrompt} 19. <h:inputTextarea value="#{user.aboutYourself}" rows="5" cols="35"/> 20. </h:panelGrid> 21. <h:commandButton value="#{msgs.submitPrompt}" action="thankYou"/> 22. </h:form> 23. </h:body> 24. </html>
Listing 4–6
personalData/web/thankYou.xhtml
1. <?xml version="1.0" encoding="UTF-8"?> 2. <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" 3. "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> 4. <html xmlns="http://www.w3.org/1999/xhtml" 5. xmlns:h="http://java.sun.com/jsf/html"> 6. <h:head> 7. <title>#{msgs.thankYouWindowTitle}</title> 8. </h:head> 9. <h:body> 10. <h:outputText value="#{msgs.namePrompt}" style="font-style: italic"/> 11. #{user.name} 12. <br/> 13. <h:outputText value="#{msgs.aboutYourselfPrompt}" style="font-style: italic"/> 14. <br/> 15. <pre>#{user.aboutYourself}</pre> 16. </h:body> 17. </html>
129
basic-components.fm Page 130 Thursday, May 6, 2010 9:51 AM
130
Chapter 4 ■ Standard JSF Tags
Listing 4–7
personalData/src/java/com/corejsf/UserBean.java
1. package com.corejsf; 2. 3. import java.io.Serializable; 4. 5. import javax.inject.Named; 6. // or import javax.faces.bean.ManagedBean; 7. import javax.enterprise.context.SessionScoped; 8. // or import javax.faces.bean.SessionScoped; 9. 10. @Named("user") // or @ManagedBean(name="user") 11. @SessionScoped 12. public class UserBean implements Serializable { 13. private String name; 14. private String password; 15. private String aboutYourself; 16. 17. public String getName() { return name; } 18. public void setName(String newValue) { name = newValue; } 19. 20. public String getPassword() { return password; } 21. public void setPassword(String newValue) { password = newValue; } 22. 23. public String getAboutYourself() { return aboutYourself; } 24. public void setAboutYourself(String newValue) { aboutYourself = newValue; } 25. }
Listing 4–8
personalData/src/java/com/corejsf/messages.properties
1. indexWindowTitle=Using Textfields and Textareas 2. thankYouWindowTitle=Thank you for submitting your information 3. thankYouPageTitle=Thank you! 4. indexPageTitle=Please enter the following personal information 5. namePrompt=Name: 6. passwordPrompt=Password: 7. tellUsPrompt=Please tell us about yourself: 8. aboutYourselfPrompt=Some information about you: 9. submitPrompt=Submit your information
basic-components.fm Page 131 Thursday, May 6, 2010 9:51 AM
Text Fields and Text Areas
Displaying Text and Images JSF applications use the following tags to display text and images: • • •
h:outputText h:outputFormat h:graphicImage
The h:outputText tag is one of JSF’s simplest tags. With only a handful of attributes, it does not typically generate an HTML element. Instead, it generates mere text—with one exception: If you specify the style or styleClass attributes, h:outputText will generate an HTML span element. In JSF 2.0, you don’t usually need the h:outputText tag since you can simply insert value expressions, such as #{msgs.namePrompt} into your page. You would use h:outputText in the following circumstances: •
To produce styled output
•
In a panel grid to make sure that the text is considered one cell of the grid
•
To generate HTML markup
The h:outputText and h:outputFormat tags have one attribute that is unique among all JSF tags: escape. By default, the escape attribute is true, which causes the characters < > & to be converted to &lt; &gt; and &amp; respectively. Changing those characters helps prevent cross-site scripting attacks. (See http://www.cert.org/ advisories/CA-2000-02.html for more information about cross-site scripting attacks.) Set this attribute to false if you want to programmatically generate HTML markup. NOTE: The value attribute of h:outputText can never contain < characters. The only way to produce HTML markup with h:outputText is with a value expression.
NOTE: When you include a value expression such as #{msgs.namePrompt} in your page, the resulting value is always escaped. You must use h:outputText if you want to generate HTML markup.
Table 4–15 lists all h:outputText attributes.
131
basic-components.fm Page 132 Thursday, May 6, 2010 9:51 AM
132
Chapter 4 ■ Standard JSF Tags
Table 4–15
Attributes for h:outputText and h:outputFormat
Attributes
Description
escape
If set to true (default), escapes <, >, and & characters
binding, converter, id, rendered, value
Basic attributesa
style, styleClass, title, dir , lang
HTML 4.0b
a. See Table 4–5 on page 107 for information about basic attributes. b. See Table 4–6 on page 110 for information about HTML 4.0 attributes.
The h:outputFormat tag formats a compound message with parameters specified in the body of the tag—for example: <h:outputFormat value="{0} is {1} years old"> <f:param value="Bill"/> <f:param value="38"/> </h:outputFormat>
In the preceding code fragment, the compound message is {0} is {1} years old and the parameters, specified with f:param tags, are Bill and 38. The output of the preceding code fragment is: Bill is 38 years old. The h:outputFormat tag uses a java.text.MessageFormat instance to format its output. The h:graphicImage tag generates an HTML img element. You can specify the image location with the url or value attribute, as a context-relative path—meaning relative to the web application’s context root. As of JSF 2.0, you can place images into the resources directory and specify a library and name: <h:graphicImage library="images" name="de_flag.gif"/>
Here, the image is located in resources/images/de_flag.gif. Alternatively, you can use this: <h:graphicImage url="/resources/images/de_flag.gif"/>
You can also use the resources map: <h:graphicImage value="#{resources['images:de_flag.gif']}"/>
Table 4–16 shows all the attributes for h:graphicImage.
basic-components.fm Page 133 Thursday, May 6, 2010 9:51 AM
Text Fields and Text Areas
Table 4–16
Attributes for h:graphicImage
Attributes
Description
binding, id, rendered, value
Basic attributesa
alt, dir, height, ismap, lang, longdesc, style, styleClass, title, url, usemap, width
HTML 4.0b
onclick, ondblclick, onkeydown, onkeypress, onkeyup, onmousedown, onmousemove, onmouseout, onmouseover, onmouseup
DHTML eventsc
library, name
The resource library and name for this image
a. See Table 4–5 on page 107 for information about basic attributes. b. See Table 4–6 on page 110 for information about HTML 4.0 attributes. c. See Table 4–7 on page 114 for information about DHTML event attributes.
Table 4–17 shows some examples of using h:outputText and h:graphicImage. Table 4–17
h:outputText and h:graphicImage Examples
Example <h:outputText value="#{form.testString}"/> <h:outputText value="Number #{form.number}"/> <h:outputText value="#{form.htmlCode}" escape="false"/> where the getHtmlCode
method returns the string "<input type='text' value='hello'/>" <h:outputText value="#{form.htmlCode}"/>
where the getHtmlCode method returns the string "<input type='text' value='hello'/>" <h:graphicImage value="/tjefferson.jpg"/> <h:graphicImage library="images" name="tjefferson.jpg" style="border: thin solid black"/>
Result
133
basic-components.fm Page 134 Thursday, May 6, 2010 9:51 AM
134
Chapter 4 ■ Standard JSF Tags
The third and fourth examples in Table 4–17 illustrate use of the escape attribute. If the value for h:outputText is <input type='text' value='hello'/>, and the escape attribute is false—as is the case for the third example in Table 4–17—the h:outputText tag generates an HTML input element. Unintentional generation of HTML elements is exactly the sort of mischief that enables miscreants to carry out cross-site scripting attacks. With the escape attribute set to true—as in the fourth example in Table 4–17—that output is transformed to harmless text, thereby thwarting a potential attack. The final two examples in Table 4–17 show you how to use h:graphicImage.
Buttons and Links Buttons and links are ubiquitous among web applications, and JSF provides the following tags to support them: • • • • •
h:commandButton h:commandLink h:button h:link h:outputLink
The h:commandButton and h:commandLink are the primary components for navigating within a JSF application. When a button or link is activated, a POST request sends the form data back to the server. JSF 2.0 introduced the h:button and h:link components. These components also render buttons and links, but clicking on them issues a bookmarkable GET request instead. We discussed this mechanism in Chapter 3. The h:outputLink tag generates an HTML anchor element that points to a resource such as an image or a web page. Clicking the generated link takes you to the designated resource without further involving the JSF framework. These links are most suitable for navigating to a different web site. Table 4–18 lists the attributes shared by h:commandButton, h:commandLink, h:button, and h:link.
basic-components.fm Page 135 Thursday, May 6, 2010 9:51 AM
Buttons and Links
Table 4–18 Attributes for h:commandButton, h:commandLink, h:button, and h:link Attribute
Description
action (h:commandButton and h:commandLink only)
If specified as a string: Directly specifies an outcome used by the navigation handler to determine the JSF page to load next as a result of activating the button or link. If specified as a method expression: The method has this signature: String methodName(); the string represents the outcome. If omitted: Activating the button or link redisplays the current page.
outcome (h:button and h:link only)
The outcome, used by the navigation handler to determine the target view when the component is rendered.
fragment (h:button and h:link only)
A fragment that is to be appended to the target URL. The # separator is applied automatically and should not be included in the fragment.
actionListener
A method expression that refers to a method with this signature: void methodName(ActionEvent).
image (h:commandButton and h:button only)
The path to an image displayed in a button. If you specify this attribute, the HTML input’s type will be image. If the path starts with a /, the application’s context root is prepended.
immediate
A Boolean. If false (the default), actions and action listeners are invoked at the end of the request life cycle; if true, actions and action listeners are invoked at the beginning of the life cycle. See Chapter 8 for more information about the immediate attribute.
type
For h:commandButton—The type of the generated input element: button, submit, or reset. The default, unless you specify the image attribute, is submit. For h:commandLink and h:link—The content type of the linked resource; for example, text/ html, image/gif, or audio/basic.
135
basic-components.fm Page 136 Thursday, May 6, 2010 9:51 AM
136
Chapter 4 ■ Standard JSF Tags
Table 4–18 Attributes for h:commandButton, h:commandLink, h:button, and h:link (cont.) Attribute
Description
value
The label displayed by the button or link. You can specify a string or a value expression.
binding, id, rendered
Basic attributes.a
accesskey, charset (h:commandLink and h:link only), coords (h:commandLink and h:link only), dir , disabled (h:commandButton only in JSF 1.1), hreflang (h:commandLink and h:link only), lang, rel (h:commandLink and h:link only), rev (h:commandLink and h:link only), shape (h:commandLink and h:link only), style, styleClass, tabindex, target (h:commandLink and h:link only), title
HTML 4.0.b
onblur, onclick, ondblclick, onfocus, onkeydown, onkeypress, onkeyup, onmousedown, onmousemove, onmouseout, onmouseover, onmouseup
DHTML events.c
a. See Table 4–5 on page 107 for information about basic attributes. b. See Table 4–6 on page 110 for information about HTML 4.0 attributes. c. See Table 4–7 on page 114 for information about DHTML event attributes.
Using Buttons The h:commandButton and h:button tags generate an HTML input element whose type is button, image, submit, or reset, depending on the attributes you specify. Table 4–19 illustrates some uses of these tags. The third example in Table 4–19 generates a push button—an HTML input element whose type is button—that does not result in a form submit. The only way to attach behavior to a push button is to specify a script for one of the DHTML event attributes, as we did for onclick in the example.
basic-components.fm Page 137 Thursday, May 6, 2010 9:51 AM
Buttons and Links
Table 4â&#x20AC;&#x201C;19
h:commandButton and h:button Examples
Example
Result
<h:commandButton value="submit" type="submit" action="#{form.submitAction}"/> <h:commandButton value="reset" type="reset"/> <h:commandButton value="click this button..." onclick="alert('button clicked')" type="button"/> <h:commandButton value="disabled" disabled="#{not form.buttonEnabled}"/> <h:button value="#{form.buttonText}" outcome="#{form.pressMeOutcome"/>
CAUTION: In JSF 1.1, there was an inconsistency in the handling of image paths between h:graphicImage and h:commandButton. The context root is automatically added by h:graphicImage, but not by h:commandButton. For example, for an application named myApp, here is how you specified the same image for each tag: <h:commandButton image="/myApp/imageFile.jpg"/> <!-- JSF 1.1 --> <h:graphicImage value="/imageFile.jpg"/> This was annoying because it required the page to know the context root. The h:commandButton behavior changed in JSF 1.2. Now the context root is automatically added if the path starts with a /. To preserve a level of annoyance, this feature interacts poorly with a resource map. You cannot use <h:commandButton image="#{resources['images:imageFile.jpg']}"/> because the string returned by the resource map starts with /context-root. The result would be <input type="image" src="/context-root/context-root/..."/>
The h:commandLink and h:link tags generates an HTML anchor element that acts like a form submit button. Table 4â&#x20AC;&#x201C;20 shows some examples.
137
basic-components.fm Page 138 Thursday, May 6, 2010 9:51 AM
138
Chapter 4 ■ Standard JSF Tags
Table 4–20
h:commandLink and h:link Examples
Example
Result
<h:commandLink>register</h:commandLink> <h:commandLink style="font-style: italic"> #{msgs.linkText}> </h:commandLink> <h:commandLink> #{msgs.linkText} <h:graphicImage value="/registration.jpg"/> </h:commandLink>
<h:commandLink value="welcome" actionListener="#{form.useLinkValue}" action="#{form.followLink}"/> <h:link value="welcome" outcome="#{form.welcomeOutcome}"> <f:param name="id" value="#{form.userId}"/> </h:link>
The h:commandLink and h:link tags generate JavaScript to make links act like buttons. For example, here is the HTML generated by the first example in Table 4–20: <a href="#" onclick="document.forms['_id0']['_id0:_id2'].value='_id0:_id2'; document.forms['_id0'].submit()">register</a>
When the user clicks the link, the anchor element’s value is set to the h:commandLink’s client ID, and the enclosing form is submitted. That submission sets the JSF life cycle in motion and, because the href attribute is "#", the current page will be reloaded unless an action associated with the link returns a non-null outcome. You can place as many children as you want in the body of an h:commandLink tag—each corresponding HTML element is part of the link. So,
for example, if you click on either the text or image in the third example in Table 4–20, the link’s form will be submitted. The next-to-last example in Table 4–20 attaches an action listener, in addition to an action, to a link. Action listeners are discussed in “Action Events” on page 312 of Chapter 8.
basic-components.fm Page 139 Thursday, May 6, 2010 9:51 AM
Buttons and Links
The last example in Table 4–20 embeds an f:param tag in the body of the h:link tag. When you click the link, a request parameter with the name and value specified with the f:param tag is created by the link. In Chapter 2, we discussed how the request parameters can be processed. You can also use request parameters with an h:commandLink or h:commandButton. See “Passing Data from the UI to the Server” on page 324 of Chapter 8 for an example. Like h:commandLink and h:link, h:outputLink generates an HTML anchor element. But unlike h:commandLink, h:outputLink does not generate JavaScript to make the link act like a submit button. The value of the h:outputLink value attribute is used for the anchor’s href attribute, and the contents of the h:outputLink body are used to populate the body of the anchor element. Table 4–21 lists all attributes for h:outputLink., and Table 4–22 shows some h:outputLink examples. Table 4–21
Attributes for h:outputLink
Attributes
Description
binding, converter, id, lang, rendered, value
Basic attributesa
accesskey, charset, coords, dir, disabled , hreflang, lang, rel, rev, shape, style, styleClass, tabindex, target, title, type
HTML 4.0b
onblur, onclick, ondblclick, onfocus, onkeydown, onkeypress, onkeyup, onmousedown, onmousemove, onmouseout, onmouseover, onmouseup
DHTML eventsc
a. See Table 4–5 on page 107 for information about basic attributes. b. See Table 4–6 on page 110 for information about HTML 4.0 attributes. c. See Table 4–7 on page 114 for information about DHTML event attributes.
Table 4–22
h:outputLink Examples
Example <h:outputLink value="http://java.net"> <h:graphicImage value="java-dot-net.jpg"/> <h:outputText value="java.net"/> </h:outputLink> <h:outputLink value="#{form.welcomeURL}"> #{form.welcomeLinkText} </h:outputLink>
Result
139
basic-components.fm Page 140 Thursday, May 6, 2010 9:51 AM
140
Chapter 4 ■ Standard JSF Tags
Table 4–22
h:outputLink Examples (cont.)
Example
Result
<h:outputLink value="#introduction"> <h:outputText value="Introduction" style="font-style: italic"/> </h:outputLink> <h:outputLink value="#conclusion" title="Go to the conclusion"> Conclusion </h:outputLink> <h:outputLink value="#toc" title="Go to the table of contents"> <h2>Table of Contents</h2> </h:outputLink>
The first example in Table 4–22 is a link to http://java.net. The second example uses properties stored in a bean for the link’s URL and text. Those properties are implemented like this: private String welcomeURL = "/outputLinks/faces/welcome.jsp"; public String getWelcomeURL() { return welcomeURL; } private String welcomeLinkText = "go to welcome page"; public String getWelcomeLinkText() { return welcomeLinkText; }
The last three examples in Table 4–22 are links to named anchors in the same JSF page. Those anchors look like this: <a name="introduction">Introduction</a> ... <a name="conclusion">Conclusion</a> ... <a name="toc">Table of Contents</a> ...
basic-components.fm Page 141 Thursday, May 6, 2010 9:51 AM
Buttons and Links
CAUTION: If you use JSF 1.1, you need to use the f:verbatim tag when you want to place text inside a tag. For example, the last example in Table 4–22 had to be: <h:outputLink...><f:verbatim>Table of Contents</f:verbatim></h:outputLink> In JSF 1.1, the text would appear outside the link. The remedy is to place the text inside another component, such as h:outputText or f:verbatim. This problem has been fixed in JSF 1.2.
Using Command Links Now that we have discussed the details of JSF tags for buttons and links, we take a look at a complete example. Figure 4–5 shows the application discussed in “Using Text Fields and Text Areas” on page 127, with two links that let you select either English or German locales. When a link is activated, an action changes the view’s locale and the JSF implementation reloads the current page.
Figure 4–5
Using command links to change locales
141
basic-components.fm Page 142 Thursday, May 6, 2010 9:51 AM
142
Chapter 4 ■ Standard JSF Tags
The links are implemented like this: <h:commandLink action="#{localeChanger.englishAction}"> <h:graphicImage library="images" name="en_flag.gif" style="border: 0px" /> </h:commandLink>
Both links specify an image and an action method. The method to change to the English locale looks like this: public class LocaleChanger { ... public String englishAction() { FacesContext context = FacesContext.getCurrentInstance(); context.getViewRoot().setLocale(Locale.ENGLISH); return null; } }
Because we have not specified any navigation for this action, the JSF implementation will reload the current page after the form is submitted. When the page is reloaded, it is localized for English or German, and the page redisplays accordingly. Figure 4–6 shows the directory structure for the application, and Listings 4–9 through 4–11 show the associated JSF pages and Java classes.
Figure 4–6
Directory structure of the flags example
basic-components.fm Page 143 Thursday, May 6, 2010 9:51 AM
Buttons and Links
Listing 4–9
flags/web/index.xhtml
1. <?xml version="1.0" encoding="UTF-8"?> 2. <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" 3. "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> 4. <html xmlns="http://www.w3.org/1999/xhtml" 5. xmlns:h="http://java.sun.com/jsf/html"> 6. <h:head> 7. <title>#{msgs.indexWindowTitle}</title> 8. </h:head> 9. <h:body> 10. <h:form> 11. <h:commandLink action="#{localeChanger.germanAction}"> 12. <h:graphicImage library="images" name="de_flag.gif" 13. style="border: 0px; margin-right: 1em;"/> 14. </h:commandLink> 15. <h:commandLink action="#{localeChanger.englishAction}"> 16. <h:graphicImage library="images" 17. name="en_flag.gif" style="border: 0px"/> 18. </h:commandLink> 19. <p><h:outputText value="#{msgs.indexPageTitle}" 20. style="font-style: italic; font-size: 1.3em"/></p> 21. <h:panelGrid columns="2"> 22. #{msgs.namePrompt} 23. <h:inputText value="#{user.name}"/> 24. #{msgs.passwordPrompt} 25. <h:inputSecret value="#{user.password}"/> 26. #{msgs.tellUsPrompt} 27. <h:inputTextarea value="#{user.aboutYourself}" rows="5" cols="35"/> 28. </h:panelGrid> 29. <h:commandButton value="#{msgs.submitPrompt}" action="thankYou"/> 30. </h:form> 31. </h:body> 32. </html>
Listing 4–10 flags/src/java/com/corejsf/UserBean.java 1. package com.corejsf; 2. 3. import java.io.Serializable; 4. 5. import javax.inject.Named; 6. // or import javax.faces.bean.ManagedBean; 7. import javax.enterprise.context.SessionScoped; 8. // or import javax.faces.bean.SessionScoped; 9.
143
basic-components.fm Page 144 Thursday, May 6, 2010 9:51 AM
144
Chapter 4 â&#x2013; Standard JSF Tags
10. @Named("user") // or @ManagedBean(name="user") 11. @SessionScoped 12. public class UserBean implements Serializable { 13. private String name; 14. private String password; 15. private String aboutYourself; 16. 17. public String getName() { return name; } 18. public void setName(String newValue) { name = newValue; } 19. 20. public String getPassword() { return password; } 21. public void setPassword(String newValue) { password = newValue; } 22. 23. public String getAboutYourself() { return aboutYourself; } 24. public void setAboutYourself(String newValue) { aboutYourself = newValue; } 25. }
Listing 4â&#x20AC;&#x201C;11 flags/src/java/com/corejsf/LocaleChanger.java 1. package com.corejsf; 2. 3. import java.io.Serializable; 4. import java.util.Locale; 5. 6. import javax.inject.Named; 7. // or import javax.faces.bean.ManagedBean; 8. import javax.enterprise.context.SessionScoped; 9. // or import javax.faces.bean.SessionScoped; 10. import javax.faces.context.FacesContext; 11. 12. @Named // or @ManagedBean 13. @SessionScoped 14. public class LocaleChanger implements Serializable { 15. public String germanAction() { 16. FacesContext context = FacesContext.getCurrentInstance(); 17. context.getViewRoot().setLocale(Locale.GERMAN); 18. return null; 19. } 20. 21. public String englishAction() { 22. FacesContext context = FacesContext.getCurrentInstance(); 23. context.getViewRoot().setLocale(Locale.ENGLISH); 24. return null; 25. } 26. }
basic-components.fm Page 145 Thursday, May 6, 2010 9:51 AM
Selection Tags
Selection Tags JSF has seven tags for making selections: • • • • • • •
h:selectBooleanCheckbox h:selectManyCheckbox h:selectOneRadio h:selectOneListbox h:selectManyListbox h:selectOneMenu h:selectManyMenu
Table 4–23 shows examples of each tag. Table 4–23
Selection Tag Examples
Tag
Generated HTML
h:selectBooleanCheckbox
<input type="checkbox">
h:selectManyCheckbox
<table> ... <label> <input type="checkbox"/> </label> ... </table>
h:selectOneRadio
<table> ... <label> <input type="radio"/> </label> ... </table>
h:selectOneListbox
<select> <option value="Cheese"> Cheese </option> ... </select>
h:selectManyListbox
<select multiple> <option value="Cheese"> Cheese </option> ... </select>
Examples
145
basic-components.fm Page 146 Thursday, May 6, 2010 9:51 AM
146
Chapter 4 ■ Standard JSF Tags
Table 4–23
Selection Tag Examples (cont.)
Tag
Generated HTML
h:selectOneMenu
<select size="1"> <option value="Cheese"> Cheese </option> ... </select>
h:selectManyMenu
<select multiple size="1"> <option value="Sunday"> Sunday </option> ... </select>
Examples
The h:selectBooleanCheckbox is the simplest selection tag—it renders a checkbox you can wire to a boolean bean property. You can also render a set of checkboxes with h:selectManyCheckbox. Tags whose names begin with selectOne let you select one item from a collection. The selectOne tags render sets of radio buttons, single-select menus, or listboxes. The selectMany tags render sets of checkboxes, multiselect menus, or listboxes. All selection tags share an almost identical set of attributes, listed in Table 4–24. Table 4–24 Attributes for h:selectBooleanCheckbox, h:selectManyCheckbox, h:selectOneRadio, h:selectOneListbox, h:selectManyListbox, h:selectOneMenu, and h:selectManyMenu Attributes
Description
enabledClass, disabledClass
CSS class for enabled or disabled elements—for h:selectOneRadio and h:selectManyCheckbox only.
selectedClass, unselectedClass
CSS class for selected or unselected elements—for h:selectManyCheckbox only.
layout
Specification for how elements are laid out: lineDirection (horizontal) or pageDirection (vertical)—for h:selectOneRadio and h:selectManyCheckbox only.
basic-components.fm Page 147 Thursday, May 6, 2010 9:51 AM
Selection Tags
Table 4–24 Attributes for h:selectBooleanCheckbox, h:selectManyCheckbox, h:selectOneRadio, h:selectOneListbox, h:selectManyListbox, h:selectOneMenu, and h:selectManyMenu (cont.) Attributes
Description
label
A description of the component for use in error messages.
collectionType
(selectMany tags only) A string or a value expression that evaluates to a fully qualified collection class name, such as java.util.TreeSet. See “The value Attribute and Multiple Selections” on page 162.
hideNoSelectionOption
Hide any item that is marked as the “no selection option”. See “The f:selectItem Tag” on page 153.
binding, converter, converterMessage , requiredMessage , id, immediate, required, rendered, validator, validatorMessage value, valueChangeListener
Basic attributes.a
,
accesskey, border, dir, disabled, lang, readonly, style, styleClass, size, tabindex, title
HTML 4.0b—border is applicable to h:selectOneRadio and h:selectManyCheckbox only. size is applicable to h:selectOneListbox and h:selectManyListbox only.
onblur, onchange, onclick, ondblclick, onfocus, onkeydown, onkeypress, onkeyup, onmousedown, onmousemove, onmouseout, onmouseover, onmouseup, onselect
DHTML events.c
a. See Table 4–5 on page 107 for information about basic attributes. b. See Table 4–6 on page 110 for information about HTML 4.0 attributes. c. See Table 4–7 on page 114 for information about DHTML event attributes.
147
basic-components.fm Page 148 Thursday, May 6, 2010 9:51 AM
148
Chapter 4 ■ Standard JSF Tags
Checkboxes and Radio Buttons Two JSF tags represent checkboxes: • •
h:selectBooleanCheckbox h:selectManyCheckbox
The h:selectBooleanCheckbox tag represents a single checkbox that you can wire to a boolean bean property. Here is an example:
In your JSF page, you do this: <h:selectBooleanCheckbox value="#{form.contactMe}"/>
In your backing bean, provide a read-write property: private boolean contactMe; public void setContactMe(boolean newValue) { contactMe = newValue; } public boolean getContactMe() { return contactMe; }
The generated HTML looks something like this: <input type="checkbox" name="_id2:_id7"/>
You can create a group of checkboxes with h:selectManyCheckbox. As the tag name implies, you can select one or more of the checkboxes in the group. You specify that group within the body of h:selectManyCheckbox, either with one or more f:selectItem tags or one f:selectItems tag. See “Items” on page 153 for more information about those core tags. For example, here is a group of checkboxes for selecting colors:
The h:selectManyCheckbox tag looks like this: <h:selectManyCheckbox value="#{form.colors}"> <f:selectItem itemValue="Red" itemLabel="Red"/> <f:selectItem itemValue="Blue" itemLabel="Blue"/> <f:selectItem itemValue="Yellow" itemLabel="Yellow"/> <f:selectItem itemValue="Green" itemLabel="Green"/> <f:selectItem itemValue="Orange" itemLabel="Orange"/> </h:selectManyCheckbox>
The checkboxes are specified with f:selectItem (page 153) or f:selectItems (page 155).
basic-components.fm Page 149 Thursday, May 6, 2010 9:51 AM
Selection Tags
The h:selectManyCheckbox tag generates an HTML table element; here is the generated HTML for our color example: <table> <tr> <td> <label for="_id2:_id14"> <input name="_id2:_id14" value="Red" type="checkbox"> Red</input> </label> </td> </tr> ... </table>
Each color is an input element, wrapped in a label for accessibility. That label is placed in a td element. Radio buttons are implemented with h:selectOneRadio. Here is an example:
The value attribute of the h:selectOneRadio tag specifies the currently selected item. Once again, we use multiple f:selectItem tags to populate the radio buttons: <h:selectOneRadio value="#{form.education}"> <f:selectItem itemValue="High School" itemLabel="High School"/> <f:selectItem itemValue="Bachelor's" itemLabel="Bachelor's"/> <f:selectItem itemValue="Master's" itemLabel="Master's"/> <f:selectItem itemValue="Doctorate" itemLabel=Doctorate"/> </h:selectOneRadio>
Like h:selectManyCheckbox, h:selectOneRadio generates an HTML table. Here is the table generated by the preceding tag: <table> <tr> <td> <label for="_id2:_id14"> <input name="_id2:_id14" value="High School" type="radio"> High School </input> </label> </td> </tr> ... </table>
149
basic-components.fm Page 150 Thursday, May 6, 2010 9:51 AM
150
Chapter 4 ■ Standard JSF Tags
Besides generating HTML tables, h:selectOneRadio and h:selectManyCheckbox have something else in common—a handful of attributes unique to those two tags: • • • •
border enabledClass disabledClass layout
The border attribute specifies the width of the border. For example, here are radio buttons and checkboxes with borders of 1 and 2, respectively:
The enabledClass and disabledClass attributes specify CSS classes used when the checkboxes or radio buttons are enabled or disabled, respectively. For example, the following picture shows an enabled class with an italic font style, blue color, and yellow background:
The layout attribute can be either lineDirection (horizontal) or pageDirection (vertical). For example, the following checkboxes on the left have a pageDirection layout and the checkboxes on the right are lineDirection:
NOTE: You might wonder why layout attribute values are not horizontal and vertical, instead of lineDirection and pageDirection, respectively. Although lineDirection and pageDirection are indeed horizontal and vertical for Latinbased languages, that is not always the case for other languages. For example, a Chinese browser that displays text top to bottom could regard lineDirection as vertical and pageDirection as horizontal.
basic-components.fm Page 151 Thursday, May 6, 2010 9:51 AM
Selection Tags
Menus and Listboxes Menus and listboxes are represented by the following tags: • • • •
h:selectOneListbox h:selectManyListbox h:selectOneMenu h:selectManyMenu
The attributes for the preceding tags are listed in Table 4–24 on page 146, so that discussion is not repeated here. Menu and listbox tags generate HTML select elements. The menu tags add a size="1" attribute to the select element. That size designation is all that separates menus and listboxes. Here is a single-select listbox:
The corresponding listbox tag looks like this: <h:selectOneListbox value="#{form.year}" size="5"> <f:selectItem itemValue="1900" itemLabel="1900"/> <f:selectItem itemValue="1901" itemLabel="1901"/> ... </h:selectOneListbox>
Notice that we’ve used the size attribute to specify the number of visible items. The generated HTML looks like this: <select name="_id2:_id11" size="5"> <option value="1900">1900</option> <option value="1901">1901</option> ... </select>
Use h:selectManyListbox for multiselect listboxes like this one:
151
basic-components.fm Page 152 Thursday, May 6, 2010 9:51 AM
152
Chapter 4 â&#x2013; Standard JSF Tags
The listbox tag looks like this: <h:selectManyListbox value="#{form.languages}"> <f:selectItem itemValue="English" itemLabel="English"/> <f:selectItem itemValue="French" itemLabel="French"/> <f:selectItem itemValue="Italian" itemLabel="Italian"/> <f:selectItem itemValue="Spanish" itemLabel="Spanish"/> <f:selectItem itemValue="Russian" itemLabel="Russian"/> </h:selectManyListbox>
This time we do not specify the size attribute, so the listbox grows to accommodate all its items. The generated HTML looks like this: <select name="_id2:_id11" multiple> <option value="English">English</option> <option value="French">French</option> ... </select>
Use h:selectOneMenu and h:selectManyMenu for menus. A single-select menu looks like this:
h:selectOneMenu created the preceding menu: <h:selectOneMenu value="#{form.day}"> <f:selectItem itemValue="1" itemLabel="Sunday"/> <f:selectItem itemValue="2" itemLabel="Monday"/> <f:selectItem itemValue="3" itemLabel="Tuesday"/> <f:selectItem itemValue="4" itemLabel="Wednesday"/> <f:selectItem itemValue="5" itemLabel="Thursday"/> <f:selectItem itemValue="6" itemLabel="Friday"/> <f:selectItem itemValue="7" itemLabel="Saturday"/> </h:selectOneMenu>
Here is the generated HTML: <select name="_id2:_id17" size="1"> <option value="1">Sunday</option> ... </select>
The h:selectManyMenu tag is used for multiselect menus. That tag generates HTML, which looks like this: <select name="_id2:_id17" multiple size="1"> <option value="1">Sunday</option> ... </select>
basic-components.fm Page 153 Thursday, May 6, 2010 9:51 AM
Selection Tags
That HTML does not yield consistent results among browsers. For example, here is h:selectManyMenu on Internet Explorer (left) and Netscape (right):
NOTE: In HTML, the distinction between menus and listboxes is artificial. Menus and listboxes are both HTML select elements. The only distinction: Menus always have a size="1" attribute. Browsers consistently render single-select menus as drop-down lists, as expected. But they do not consistently render multiple select menus, specified with size="1" and multiple attributes. Instead of rendering a drop-down list with multiple selection, as you might expect, some browsers render absurdities such as tiny scrollbars that are nearly impossible to manipulate (Internet Explorer) or no scrollbar at all, leaving you to navigate with arrow keys (Firefox).
Items Starting with “Checkboxes and Radio Buttons” on page 148, we have used multiple f:selectItem tags to populate select components. Now that we are familiar with the visual appearance of selection tags, we take a closer look at f:selectItem and the related f:selectItems tags. The f:selectItem Tag You use f:selectItem to specify single selection items, like this: <h:selectOneMenu value="#{form.condiments}"> <f:selectItem itemValue="Cheese" itemLabel="Cheese"/> <f:selectItem itemValue="Pickle" itemLabel="Pickle"/> <f:selectItem itemValue="Mustard" itemLabel="Mustard"/> <f:selectItem itemValue="Lettuce" itemLabel="Lettuce"/> <f:selectItem itemValue="Onions" itemLabel="Onions"/> </h:selectOneMenu>
The values—Cheese, Pickle, etc.—are transmitted as request parameter values when a selection is made from the menu and the menu’s form is subsequently submitted. The itemLabel values are used as labels for the menu items. Sometimes you want to specify different values for request parameter values and item labels:
153
basic-components.fm Page 154 Thursday, May 6, 2010 9:51 AM
154
Chapter 4 ■ Standard JSF Tags
<h:selectOneMenu value="#{form.condiments}"> <f:selectItem itemValue="1" itemLabel="Cheese"/> <f:selectItem itemValue="2" itemLabel="Pickle"/> <f:selectItem itemValue="3" itemLabel="Mustard"/> <f:selectItem itemValue="4" itemLabel="Lettuce"/> <f:selectItem itemValue="5" itemLabel="Onions"/> </h:selectOneMenu>
In the preceding code, the item values are strings. “Binding the value Attribute” on page 161 shows you how to use different data types for item values. In addition to labels and values, you can also supply item descriptions and specify an item’s disabled state: <f:selectItem itemLabel="Cheese" itemValue="#{form.cheeseValue}" itemDescription="used to be milk" itemDisabled="true"/>
Item descriptions are for tools only—they do not affect the generated HTML. The itemDisabled attribute, however, is passed to HTML. The f:selectItem tag has the attributes shown in Table 4–25. As of JSF 2.0, there is a noSelectionOption attribute for marking an item that is included for navigational purposes, such as “Select a condiment”. This attribute is used in conjunction with validation. If an entry is required and the user selects the “no selection option”, a validation error occurs. Table 4–25
Attributes for f:selectItem
Attribute
Description
binding, id
Basic attributesa
itemDescription
Description used by tools only
itemDisabled
Boolean value that sets the item’s disabled HTML attribute
itemLabel
Text shown by the item
itemValue
Item’s value, passed to the server as a request parameter
value
Value expression that points to a SelectItem instance
escape
true if special characters in the value should be converted to character entities (default), false if the value should be emitted without change
noSelectionOption
true if this item is the “no selection” option that, when selected, indicates that the user intends to made no selection
a. See Table 4–5 on page 107 for information about basic attributes.
basic-components.fm Page 155 Thursday, May 6, 2010 9:51 AM
Selection Tags
You can use f:selectItem’s value attribute to access SelectItem instances created in a bean: <f:selectItem value="#{form.cheeseItem}"/>
The value expression for the value attribute points to a method that returns a javax.faces.model.SelectItem instance: public SelectItem getCheeseItem() { return new SelectItem("Cheese"); }
javax.faces.model.SelectItem
•
SelectItem(Object value)
Creates a SelectItem with a value. The item label is obtained by applying toString() to the value. •
SelectItem(Object value, String label)
Creates a SelectItem with a value and a label. •
SelectItem(Object value, String label, String description)
Creates a SelectItem with a value, label, and description. •
SelectItem(Object value, String label, String description, boolean disabled)
Creates a SelectItem with a value, label, description, and disabled state. •
SelectItem(Object value, String label, String description, boolean disabled, boolean noSelectionOption)
Creates a SelectItem with a value, label, description, disabled state, and “no selection option” flag. The f:selectItems Tag As we saw in “The f:selectItem Tag” on page 153, f:selectItem is versatile, but it is tedious for specifying more than a few items. The first code fragment shown in that section can be reduced to the following with f:selectItems: <h:selectOneRadio value="#{form.condiments}> <f:selectItems value="#{form.condimentItems}"/> </h:selectOneRadio>
The value expression #{form.condimentItems} could point to an array of SelectItem instances: private static SelectItem[] condimentItems = { new SelectItem(1, "Cheese"), new SelectItem(2, "Pickle"), new SelectItem(3, "Mustard"), new SelectItem(4, "Lettuce"),
155
basic-components.fm Page 156 Thursday, May 6, 2010 9:51 AM
156
Chapter 4 ■ Standard JSF Tags
new SelectItem(5, "Onions") }; public SelectItem[] getCondimentItems() { return condimentItems; }
The f:selectItems value attribute must be a value expression that points to one of the following: •
A single SelectItem instance
•
A collection
•
An array
•
A map whose entries represent labels and values
The first option is not very useful. We discuss the other options in the following sections. NOTE: Can’t remember what you can specify for the f:selectItems value attribute? It’s a SCAM: Single select item, Collection, Array, or Map.
NOTE: A single f:selectItems tag is usually better than multiple f:selectItem tags. If the number of items changes, you have to modify only Java code if you use f:selectItems, whereas f:selectItem may require you to modify both Java code and JSF pages.
Table 4–26 summarizes the attributes of the f:selectItems tag. Table 4–26
Attributes for f:selectItems
Attribute
Description
binding, id
Basic attributesa
value
Value expression that points to a SelectItem instance, an array or collection, or a map
var
The name of a variable, used in the value expressions below when traversing an array or collection of objects other than SelectItem
itemLabel
Value expression yielding the text shown by the item referenced by the var variable
basic-components.fm Page 157 Thursday, May 6, 2010 9:51 AM
Selection Tags
Table 4–26
Attributes for f:selectItems (cont.)
Attribute
Description
itemValue
Value expression yielding the value of the item referenced by the var variable
itemDescription
Value expression yielding the description of the item referenced by the var variable; the description is intended for use by tools
itemDisabled
Value expression yielding the disabled HTML attribute of the item referenced by the var variable
itemLabelEscaped
Value expression yielding true if special characters in the item’s value should be converted to character entities (default), false if the value should be emitted without change
noSelectionOption
Value expression that yields the “no selection option” item or string that equals the value of the “no selection option” item
a. See Table 4–5 on page 107 for information about basic attributes.
Using Collections and Arrays with f:selectItems Before JSF 2.0, collections and arrays had to contain SelectItem instances. That was unfortunate because it coupled your business logic to the JSF API. As of JSF 2.0, the value of f:selectItems can be a collection or array containing objects of any type. If they are instances of SelectItem, no further processing is done. Otherwise, the labels are obtained by calling toString on each object. Alternatively, you cau use the var attribute to define a variable that iterates over the array or collection. Then you supply value expressions for the label and value in the attributes itemLabel and itemValue. For example, suppose you want users to select objects of the following class: public class Weekday { public String getDayName() { ... } // name in current locale, such as "Monday" public int getDayNumber() { ... } // number such as Calendar.MONDAY (2) ... }
Use the following tag: <f:selectItems value="#{form.daysOfTheWeek}" var="w"
157
basic-components.fm Page 158 Thursday, May 6, 2010 9:51 AM
158
Chapter 4 ■ Standard JSF Tags
itemLabel="#{w.dayName}" itemValue="#{w.dayNumber}" />
Here, #{form.daysOfTheWeek} yields an array or collection of Weekday objects. The variable w is set to each of the elements. Then a SelectItem object is constructed with the results of the itemLabel and itemValue expressions. NOTE: The var attribute in the f:selectItems tag is conceptually similar to the use of the var attribute in the h:dataTable which we will discuss in Chapter 6.
Using Maps with f:selectItems If the value attribute of the f:selectItems tag yields a map, the JSF implementation creates a SelectItem instance for every entry in the map. The entry’s key is used as the item’s label, and the entry’s value is used as the item’s value. For example, here are condiments specified with a map: private static Map<String, Object> condimentItems; static { condimentItems = new LinkedHashMap<String, Object>(); condimentItems.put("Cheese", 1); // label, value condimentItems.put("Pickle", 2); condimentItems.put("Mustard", 3); condimentItems.put("Lettuce", 4); condimentItems.put("Onions", 5); } public Map<String, Object> getCondimentItems() { return condimentItems; }
Note that you cannot specify item descriptions or disabled status when you use a map. Pay attention to these two issues when using a map: 1.
You will generally want to use a LinkedHashMap, not a TreeMap or HashMap. In a LinkedHashMap, you can control the order of the items because items are visited in the order in which they were inserted. If you use a TreeMap, the labels that are presented to the user (which are the keys of the map) are sorted alphabetically. That may or may not be what you want. For example, days of the week would be neatly arranged as Friday Monday Saturday Sunday Thursday Tuesday Wednesday. If you use a HashMap, the items are ordered randomly.
basic-components.fm Page 159 Thursday, May 6, 2010 9:51 AM
Selection Tags
2.
Map keys are turned into item labels and map values into item values. When a user selects an item, your backing bean receives a value in your map, not a key. For example, in the example above, if the backing bean receives a value of 5, you would need to iterate through the entries if you wanted to find the matching "Onions". Since the value is probably more meaningful to your application than the label, this is usually not a problem, just something to be aware of.
Item Groups You can group menu or listbox items together, like this:
Here are the JSF tags that define the listbox: <h:selectManyListbox> <f:selectItems value="#{form.menuItems}"/> </h:selectManyListbox>
The menuItems property is a SelectItem array: public SelectItem[] getMenuItems() { return menuItems; }
The menuItems array is instantiated like this: private static SelectItem[] menuItems = { burgers, beverages, condiments };
The burgers, beverages, and condiments variables are SelectItemGroup instances that are instantiated like this: private SelectItemGroup burgers = new SelectItemGroup("Burgers", "burgers on the menu", false, burgerItems);
// // // //
value description disabled select items
159
basic-components.fm Page 160 Thursday, May 6, 2010 9:51 AM
160
Chapter 4 â&#x2013; Standard JSF Tags
private SelectItemGroup beverages = new SelectItemGroup("Beverages", "beverages on the menu", false, beverageItems); private SelectItemGroup condiments = new SelectItemGroup("Condiments", "condiments on the menu", false, condimentItems);
// // // //
// // // //
value description disabled select items
value description disabled select items
Notice that we are using SelectItemGroups to populate an array of SelectItems. We can do that because SelectItemGroup extends SelectItem. The groups are created and initialized like this: private SelectItem[] burgerItems = { new SelectItem("Qwarter pounder"), new SelectItem("Single"), new SelectItem("Veggie"), }; private SelectItem[] beverageItems = { new SelectItem("Coke"), new SelectItem("Pepsi"), new SelectItem("Water"), new SelectItem("Coffee"), new SelectItem("Tea"), }; private SelectItem[] condimentItems = { new SelectItem("cheese"), new SelectItem("pickle"), new SelectItem("mustard"), new SelectItem("lettuce"), new SelectItem("onions"), }; SelectItemGroup instances encode HTML optgroup elements. For example, the
preceding code generates the following HTML: <select name="_id0:_id1" multiple size="16"> <optgroup label="Burgers"> <option value="1" selected>Qwarter pounder</option> <option value="2">Single</option> <option value="3">Veggie</option> </optgroup>
basic-components.fm Page 161 Thursday, May 6, 2010 9:51 AM
Selection Tags
<optgroup label="Beverages"> <option value="4" selected>Coke</option> <option value="5">Pepsi</option> <option value="6">Water</option> <option value="7">Coffee</option> <option value="8">Tea</option> </optgroup> <optgroup label="Condiments"> <option value="9">cheese</option> <option value="10">pickle</option> <option value="11">mustard</option> <option value="12">lettuce</option> <option value="13">onions</option> </optgroup> </select> NOTE: The HTML 4.01 specification does not allow nested optgroup elements, which would be useful for things like cascading menus. The specification does mention that future HTML versions may support that behavior.
javax.faces.model.SelectItemGroup
•
SelectItemGroup(String label)
Creates a group with a label but no selection items. •
SelectItemGroup(String label, String description, boolean disabled, SelectItem[] items)
Creates a group with a label, a description (which is ignored by the JSF Reference Implementation), a boolean that disables all the items when true, and an array of select items used to populate the group. •
setSelectItems(SelectItem[] items)
Sets a group’s array of SelectItems. Binding the value Attribute Whether you are using a set of checkboxes, a menu, or a listbox, you will want to keep track of the item or items selected by the user. For that purpose, you use the value attribute of the selectOne and selectMany tags. Consider this example: <h:selectOneMenu value="#{form.bestDay}"> <f:selectItems value="#{form.weekdays}"/> </h:selectOneRadio>
161
basic-components.fm Page 162 Thursday, May 6, 2010 9:51 AM
162
Chapter 4 â&#x2013; Standard JSF Tags
The value attribute of h:selectOneMenu refers to the value that the user selects. The value attribute of f:selectItems specifies all possible values. Suppose the radio buttons were specified with an array of SelectItem objects, containing the following: new SelectItem(1, "Sunday"), new SelectItem(2, "Monday"), ...
// value, label
The user sees the labels (Sunday, Monday, ...), but the application uses the values (1, 2, ...). There is an important but subtle issue about the Java type of the values. In the web page, the values are always strings: <option value="1">Sunday</option> <option value="2">Monday</option>
When the page is submitted, the server receives the selected string and must convert it to an appropriate type. The JSF implementation knows how to convert to numbers and enumerated types, but for other types you need to define a converter. (We discuss converters in Chapter 7.) In our example, the #{form.bestDay} value expression should refer to a property of type int or Integer. Listing 4â&#x20AC;&#x201C;13 has an example where the value is an enumerated type. CAUTION: Because the value of a SelectItem is an Object, it can be tempting to set it to the value that you actually need in your application. However, keep in mind that the value is turned into a string when it is sent to the client. For example, consider a SelectItem(Color.RED, "Red"). The client receives the client is the string "java.awt.Color[r=255,g=0,b=0]". That string is returned when the user selects the option with label "Red". You would have to parse it to turn it back into a color. It is easier to send the RGB value of the color instead.
The value Attribute and Multiple Selections You can keep track of multiple selections with a selectMany tag. These tags have a value attribute that specifies zero or more selected items, using an array or collection. Consider an h:selectManyListbox that lets a user choose multiple condiments: <h:selectManyListbox value="#{form.condiments}"> <f:selectItems value="#{form.condimentItems}"/> </h:selectManyListbox>
basic-components.fm Page 163 Thursday, May 6, 2010 9:51 AM
Selection Tags
Here are the condimentItems and condiments properties: private static SelectItem[] condimentItems = { new SelectItem(1, "Cheese"), new SelectItem(2, "Pickle"), new SelectItem(3, "Mustard"), new SelectItem(4, "Lettuce"), new SelectItem(5, "Onions"), }; public SelectItem[] getCondimentItems() { return condimentItems; } private int[] condiments; public void setCondiments(int[] newValue) { condiments = newValue; } public int[] getCondiments() { return condiments; }
Instead of an int[] array for the condiments property, you could have used an Integer[] array. The value of a selectMany tag can be a collection instead of an array, but there are two technical issues that you need to keep in mind. Most importantly, the elements cannot be converted because the collectionâ&#x20AC;&#x2122;s element type is not known at runtime. (This is an unfortunate aspect of Java generics. At runtime, an ArrayList<Integer> or ArrayList<String> is only a raw ArrayList, and there is no way of determining the element type. In contrast, Integer[] and String[] are distinct types at runtime.) That means, you should use collections only for strings. The other complexity is more subtle. When the JSF application receives the user choices, it must construct a new instance of the collection, populate it, and pass the collection to the property setter. But suppose the property type is Set<String>. What kind of Set should be constructed? Before JSF 2.0, this was not clearly specified. JSF 2.0 lays down the following rules: 1.
If the tag has a collectionType attribute, its value must be a string or a value expression that evaluates to a fully qualified classname, such as java.util.TreeSet. Instantiate that class.
2.
Otherwise, get the existing value and try cloning and clearing it.
163
basic-components.fm Page 164 Thursday, May 6, 2010 9:51 AM
164
Chapter 4 ■ Standard JSF Tags
3.
If that fails (perhaps because the existing value was null or not cloneable), look at the type of the value expression. If that type is SortedSet, Set, or Queue, construct a TreeSet, HashSet, or LinkedList.
4.
Otherwise, construct an ArrayList.
For example, suppose you define a languages property: private Set<String> languages; // initialized with null public Set<String> getLanguages() { return languages; } public void setLanguages(Set<String> newValue) { languages = newValue; }
When the form is submitted for the first time, the property setter is called with a HashSet that contains the user choices (step 3). In subsequent invocations, that set is cloned (step 2). However, suppose you initialize the set: private Set<String> languages = new TreeSet();
Then a clone of that TreeSet is always returned. All Together: Checkboxes, Radio Buttons, Menus, and Listboxes We close out our section on selection tags with an example that exercises nearly all those tags. That example, shown in Figure 4–7, implements a form requesting personal information. We use an h:selectBooleanCheckbox to determine whether the user wants to be contacted, and h:selectOneMenu lets the user select the best day of the week for us to do so. The year listbox is implemented with h:selectOneMenu, and it demonstrates the use of a “no selection” item. The language checkboxes are implemented with h:selectManyCheckbox; the education level is implemented with h:selectOneRadio. Note that the languages are collected in a Set<String>. Also note the styles in the color selector. The disabled Orange option is colored gray, and the selected colors are marked in bold. We use the attribute onchange="submit()" in order to update the styles immediately upon selection. When the user submits the form, JSF navigation takes us to a JSF page that shows the data the user entered. The directory structure for the application shown in Figure 4–7 is shown in Figure 4–8. The JSF pages, RegisterForm bean, faces configuration file, and resource bundle are shown in Listings 4–12 through 4–16.
basic-components.fm Page 165 Thursday, May 6, 2010 9:51 AM
Selection Tags
Figure 4â&#x20AC;&#x201C;7
Using checkboxes, radio buttons, menus, and listboxes
Figure 4â&#x20AC;&#x201C;8 The directory structure of the selection example
165
basic-components.fm Page 166 Thursday, May 6, 2010 9:51 AM
166
Chapter 4 ■ Standard JSF Tags
Listing 4–12 select/web/index.xhtml 1. <?xml version="1.0" encoding="UTF-8"?> 2. <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" 3. "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> 4. <html xmlns="http://www.w3.org/1999/xhtml" 5. xmlns:f="http://java.sun.com/jsf/core" xmlns:h="http://java.sun.com/jsf/html"> 6. <h:head> 7. <h:outputStylesheet library="css" name="styles.css"/> 8. <title>#{msgs.indexWindowTitle}</title> 9. </h:head> 10. <h:body> 11. 12. <h:outputText value="#{msgs.indexPageTitle}" styleClass="emphasis"/> 13. <h:form> 14. <h:panelGrid columns="2"> 15. #{msgs.namePrompt} 16. <h:inputText value="#{form.name}"/> 17. #{msgs.contactMePrompt} 18. <h:selectBooleanCheckbox value="#{form.contactMe}"/> 19. #{msgs.bestDayPrompt} 20. <h:selectManyMenu value="#{form.bestDaysToContact}"> 21. <f:selectItems value="#{form.daysOfTheWeek}" var="w" 22. itemLabel="#{w.dayName}" itemValue="#{w.dayNumber}"/> 23. </h:selectManyMenu> 24. #{msgs.yearOfBirthPrompt} 25. <h:selectOneMenu value="#{form.yearOfBirth}" required="true"> 26. <f:selectItems value="#{form.yearItems}"/> 27. </h:selectOneMenu> 28. #{msgs.colorPrompt} 29. <h:selectManyCheckbox value="#{form.colors}" 30. selectedClass="selected" disabledClass="disabled" 31. onchange="submit()"> 32. <f:selectItems value="#{form.colorItems}"/> 33. </h:selectManyCheckbox> 34. #{msgs.languagePrompt} 35. <h:selectManyListbox size="5" value="#{form.languages}"> 36. <f:selectItems value="#{form.languageItems}"/> 37. </h:selectManyListbox> 38. #{msgs.educationPrompt} 39. <h:selectOneRadio value="#{form.education}" 40. selectedClass="selected" layout="pageDirection"> 41. <f:selectItems value="#{form.educationItems}"/> 42. </h:selectOneRadio> 43. </h:panelGrid> 44. <h:commandButton value="#{msgs.buttonPrompt}" action="showInformation"/>
basic-components.fm Page 167 Thursday, May 6, 2010 9:51 AM
Selection Tags
45. </h:form> 46. <h:messages/> 47. </h:body> 48. </html>
Listing 4â&#x20AC;&#x201C;13 select/web/showInformation.xhtml 1. <?xml version="1.0" encoding="UTF-8"?> 2. <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" 3. "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> 4. <html xmlns="http://www.w3.org/1999/xhtml" 5. xmlns:f="http://java.sun.com/jsf/core" xmlns:h="http://java.sun.com/jsf/html"> 6. <h:head> 7. <title>#{msgs.indexWindowTitle}</title> 8. </h:head> 9. <h:body> 10. <h:form> 11. <h:outputStylesheet library="css" name="styles.css" target="head"/> 12. <h:outputFormat value="#{msgs.thankYouLabel}"> 13. <f:param value="#{form.name}"/> 14. </h:outputFormat> 15. <h:panelGrid columns="2"> 16. #{msgs.contactMeLabel} 17. <h:outputText value="#{form.contactMe}"/> 18. #{msgs.bestDayLabel} 19. <h:outputText value="#{form.bestDaysConcatenated}"/> 20. #{msgs.yearOfBirthLabel} 21. <h:outputText value="#{form.yearOfBirth}"/> 22. #{msgs.languageLabel} 23. <h:outputText value="#{form.languages}"/> 24. #{msgs.colorLabel} 25. <h:outputText value="#{form.colorsConcatenated}"/> 26. #{msgs.educationLabel} 27. <h:outputText value="#{form.education}"/> 28. </h:panelGrid> 29. <h:commandButton value="#{msgs.backPrompt}" action="index"/> 30. </h:form> 31. </h:body> 32. </html>
167
basic-components.fm Page 168 Thursday, May 6, 2010 9:51 AM
168
Chapter 4 â&#x2013; Standard JSF Tags
Listing 4â&#x20AC;&#x201C;14 select/src/java/com/corejsf/RegisterForm.java 1. package com.corejsf; 2. 3. import java.awt.Color; 4. import java.io.Serializable; 5. import java.text.DateFormatSymbols; 6. import java.util.ArrayList; 7. import java.util.Arrays; 8. import java.util.Calendar; 9. import java.util.Collection; 10. import java.util.LinkedHashMap; 11. import java.util.Map; 12. import java.util.Set; 13. import java.util.TreeSet; 14. 15. import javax.inject.Named; 16. // or import javax.faces.bean.ManagedBean; 17. import javax.enterprise.context.SessionScoped; 18. // or import javax.faces.bean.SessionScoped; 19. import javax.faces.model.SelectItem; 20. 21. @Named("form") // or @ManagedBean(name="form") 22. @SessionScoped 23. public class RegisterForm implements Serializable { 24. public enum Education { HIGH_SCHOOL, BACHELOR, MASTER, DOCTOR }; 25. public static class Weekday { 26. 27. private int dayOfWeek; 28. public Weekday(int dayOfWeek) { 29. this.dayOfWeek = dayOfWeek; 30. } 31. 32. public String getDayName() { 33. DateFormatSymbols symbols = new DateFormatSymbols(); 34. String[] weekdays = symbols.getWeekdays(); 35. return weekdays[dayOfWeek]; 36. } 37. 38. public int getDayNumber() { 39. return dayOfWeek; 40. } 41. } 42. 43. private String name; 44. private boolean contactMe;
basic-components.fm Page 169 Thursday, May 6, 2010 9:51 AM
Selection Tags
45. 46. 47. 48. 49. 50. 51. 52. 53. 54. 55. 56. 57. 58. 59. 60. 61. 62. 63. 64. 65. 66. 67. 68. 69. 70. 71. 72. 73. 74. 75. 76. 77. 78. 79. 80. 81. 82. 83. 84. 85. 86. 87. 88. 89. 90. 91.
private private private private private
int[] bestDaysToContact; Integer yearOfBirth; int[] colors; Set<String> languages = new TreeSet<String>(); Education education = Education.BACHELOR;
public String getName() { return name; } public void setName(String newValue) { name = newValue; } public boolean getContactMe() { return contactMe; } public void setContactMe(boolean newValue) { contactMe = newValue; } public int[] getBestDaysToContact() { return bestDaysToContact; } public void setBestDaysToContact(int[] newValue) { bestDaysToContact = newValue; } public Integer getYearOfBirth() { return yearOfBirth; } public void setYearOfBirth(Integer newValue) { yearOfBirth = newValue; } public int[] getColors() { return colors; } public void setColors(int[] newValue) { colors = newValue; } public Set<String> getLanguages() { return languages; } public void setLanguages(Set<String> newValue) { languages = newValue; } public Education getEducation() { return education; } public void setEducation(Education newValue) { education = newValue; } public Collection<SelectItem> getYearItems() { return birthYears; } public Weekday[] getDaysOfTheWeek() { return daysOfTheWeek; } public SelectItem[] getLanguageItems() { return languageItems; } public SelectItem[] getColorItems() { return colorItems; } public Map<String, Education> getEducationItems() { return educationItems; } public String getBestDaysConcatenated() { return Arrays.toString(bestDaysToContact); } public String getColorsConcatenated() { StringBuilder result = new StringBuilder(); for (int color : colors) result.append(String.format("%06x ", color)); return result.toString(); }
169
basic-components.fm Page 170 Thursday, May 6, 2010 9:51 AM
170
Chapter 4 â&#x2013; Standard JSF Tags
92. 93. 94. 95. 96. 97. 98. 99. 100. 101. 102. 103. 104. 105. 106. 107. 108. 109. 110. 111. 112. 113. 114. 115. 116. 117. 118. 119. 120. 121. 122. 123. 124. 125. 126. 127. 128. 129. 130. 131. 132.}
private SelectItem[] colorItems = { new SelectItem(Color.RED.getRGB(), "Red"), // value, label new SelectItem(Color.GREEN.getRGB(), "Green"), new SelectItem(Color.BLUE.getRGB(), "Blue"), new SelectItem(Color.YELLOW.getRGB(), "Yellow"), new SelectItem(Color.ORANGE.getRGB(), "Orange", "", true) // disabled }; private static Map<String, Education> educationItems; static { educationItems = new LinkedHashMap<String, Education>(); educationItems.put("High School", Education.HIGH_SCHOOL); // label, value educationItems.put("Bachelor's", Education.BACHELOR); educationItems.put("Master's", Education.MASTER); educationItems.put("Doctorate", Education.DOCTOR); }; private static SelectItem[] languageItems = { new SelectItem("English"), new SelectItem("French"), new SelectItem("Russian"), new SelectItem("Italian"), new SelectItem("Esperanto", "Esperanto", "", true) // disabled }; private static Collection<SelectItem> birthYears; static { birthYears = new ArrayList<SelectItem>(); // The first item is a "no selection" item birthYears.add(new SelectItem(null, "Pick a year:", "", false, false, true)); for (int i = 1900; i < 2020; ++i) birthYears.add(new SelectItem(i)); } private static Weekday[] daysOfTheWeek; static { daysOfTheWeek = new Weekday[7]; for (int i = Calendar.SUNDAY; i <= Calendar.SATURDAY; i++) { daysOfTheWeek[i - Calendar.SUNDAY] = new Weekday(i); } }
basic-components.fm Page 171 Thursday, May 6, 2010 9:51 AM
Messages
Listing 4–15 select/src/java/com/corejsf/messages.properties 1. indexWindowTitle=Checkboxes, Radio buttons, Menus, and Listboxes 2. indexPageTitle=Please fill out the following information 3. 4. namePrompt=Name: 5. contactMePrompt=Contact me 6. bestDayPrompt=What's the best day to contact you? 7. yearOfBirthPrompt=What year were you born? 8. buttonPrompt=Submit information 9. backPrompt=Back 10. languagePrompt=Select the languages you speak: 11. educationPrompt=Select your highest education level: 12. emailAppPrompt=Select your email application: 13. colorPrompt=Select your favorite colors: 14. 15. thankYouLabel=Thank you {0}, for your information 16. contactMeLabel=Contact me: 17. bestDayLabel=Best day to contact you: 18. yearOfBirthLabel=Your year of birth: 19. colorLabel=Colors: 20. languageLabel=Languages: 21. educationLabel=Education:
Listing 4–16 select/web/resources/css/styles.css 1. .emphasis { 2. font-style: italic; 3. font-size: 1.3em; 4. } 5. .disabled { 6. color: gray; 7. } 8. .selected { 9. font-weight: bold; 10. }
Messages During the JSF life cycle, any object can create a message and add it to a queue of messages maintained by the faces context. At the end of the life cycle—in the Render Response phase—you can display those messages in a view. Typically, messages are associated with a particular component and indicate either conversion or validation errors.
171
basic-components.fm Page 172 Thursday, May 6, 2010 9:51 AM
172
Chapter 4 ■ Standard JSF Tags
Although error messages are usually the most prevalent message type in a JSF application, messages come in four varieties: •
Information
•
Warning
•
Error
•
Fatal
All messages can contain a summary and a detail. For example, a summary might be Invalid Entry and a detail might be The number entered was greater than the maximum. JSF applications use two tags to display messages in JSF pages: h:messages and h:message.
The h:messages tag displays all messages that were stored in the faces context during the course of the JSF life cycle. You can restrict those messages to global messages—meaning messages not associated with a component—by setting h:message’s globalOnly attribute to true. By default, that attribute is false. The h:message tag displays a single message for a particular component. That component is designated with h:message’s mandatory for attribute. If more than one message has been generated for a component, h:message shows only the last one. NOTE: When you use JSF 2.0 and your project stage is set to Development, then an h:messages child is automatically added to your page (provided you didn’t add one yourself).
The h:message and h:messages tags share many attributes. Table 4–27 lists all attributes for both tags. Table 4–27
Attributes for h:message and h:messages
Attributes
Description
errorClass
CSS class applied to error messages.
errorStyle
CSS style applied to error messages.
fatalClass
CSS class applied to fatal messages.
fatalStyle
CSS style applied to fatal messages.
basic-components.fm Page 173 Thursday, May 6, 2010 9:51 AM
Messages
Table 4–27
Attributes for h:message and h:messages (cont.)
Attributes
Description
for
The id of the component for which to display the message (h:message only).
globalOnly
Instruction to display only global messages—applicable only to h:messages. Default is false.
infoClass
CSS class applied to information messages.
infoStyle
CSS style applied to information messages.
layout
Specification for message layout: "table" or "list"— applicable only to h:messages.
showDetail
A Boolean that determines whether message details are shown. Defaults are false for h:messages, true for h:message.
showSummary
A Boolean that determines whether message summaries are shown. Defaults are true for h:messages, false for h:message.
tooltip
A Boolean that determines whether message details are rendered in a tooltip; the tooltip is only rendered if showDetail and showSummary are true.
warnClass
CSS class for warning messages.
warnStyle
CSS style for warning messages.
binding, id, rendered
Basic attributes.a
style, styleClass, title, dir lang
HTML 4.0.b ,
a. See Table 4–5 on page 107 for information about basic attributes. b. See Table 4–6 on page 110 for information about HTML 4.0 attributes.
The majority of the attributes in Table 4–27 represent CSS classes or styles that h:message and h:messages apply to particular types of messages. You can also specify whether you want to display a message’s summary or detail, or both, with the showSummary and showDetail attributes, respectively. The h:messages layout attribute can be used to specify how messages are laid out, either as a list or a table. If you specify true for the tooltip attribute and you have
173
basic-components.fm Page 174 Thursday, May 6, 2010 9:51 AM
174
Chapter 4 ■ Standard JSF Tags
also set showDetail and showSummary to true, the message’s detail will be wrapped in a tooltip that is shown when the mouse hovers over the error message. Now that we have a grasp of message fundamentals, we take a look at an application that uses the h:message and h:messages tags. The application shown in Figure 4–9 contains a simple form with two text fields. Both text fields have required attributes. Moreover, the “Age” text field is wired to an integer property, so its value is converted automatically by the JSF framework. Figure 4–9 shows the error messages generated by the JSF framework when we neglect to specify a value for the “Name” field and provide the wrong type of value for the Age field.
Figure 4–9
Displaying messages
At the top of the JSF page, we use h:messages to display all messages. We use h:message to display messages for each input field: <h:form> <h:messages layout="table" errorClass="errors"/> ... <h:inputText id="name" value="#{user.name}" required="true" label="#{msgs.namePrompt}"/> <h:message for="name" errorClass="errors"/> ... <h:inputText id="age" value="#{form.age}" required="true" label="#{msgs.agePrompt}"/> <h:message for="age" errorClass="errors"/> ... </h:form>
basic-components.fm Page 175 Thursday, May 6, 2010 9:51 AM
Messages
Note that the input fields have label attributes that describe the fields. These labels are used in the error messages—for example, the Age: label (generated by #{msgs.agePrompt}) in this message: Age: 'old' must be a number between -2147483648 and 2147483647 Example: 9346
Both message tags in our example specify a CSS class named errors, which is defined in styles.css. That class definition looks like this: .errors { font-style: italic; color: red; }
We have also specified layout="table" for the h:messages tag. If we had omitted that attribute (or alternatively specified layout="list"), the output would look like that shown in Figure 4–10.
Figure 4–10
Messages displayed as a list
The list layout encodes the error messages in an unnumbered list (whose appearance you can control through styles). CAUTION: In JSF 1.1, the "list" style placed the messages one after the other, without any separators, which was not very useful.
Figure 4–11 shows the directory structure for the application shown in Figure 4–9. Listings 4–17 through 4–19 list the JSF page, resource bundle, and stylesheet for the application. For this example, we added getAge and setAge methods to the UserBean class.
175
basic-components.fm Page 176 Thursday, May 6, 2010 9:51 AM
176
Chapter 4 ■ Standard JSF Tags
NOTE: By default, h:messages shows message summaries but not details. h:message, on the other hand, shows details but not summaries. If you use h:messages and h:message together, as we did in the preceding example, summaries will appear at the top of the page, with details next to the appropriate input field.
Figure 4–11
Directory structure for the messages example
Listing 4–17 messages/web/index.xhtml 1. <?xml version="1.0" encoding="UTF-8"?> 2. <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" 3. "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> 4. <html xmlns="http://www.w3.org/1999/xhtml" 5. xmlns:f="http://java.sun.com/jsf/core" xmlns:h="http://java.sun.com/jsf/html"> 6. <h:head> 7. <title>#{msgs.windowTitle}</title> 8. <h:outputStylesheet library="css" name="styles.css"/> 9. </h:head> 10. <h:body> 11. <h:form> 12. <h:outputText value="#{msgs.greeting}" styleClass="emphasis"/> 13. <br/> 14. <h:messages errorClass="errors" layout="table"/> 15. <h:panelGrid columns="3"> 16. #{msgs.namePrompt}: 17. <h:inputText id="name" value="#{user.name}" required="true" 18. label="#{msgs.namePrompt}"/>
basic-components.fm Page 177 Thursday, May 6, 2010 9:51 AM
Conclusion
19. <h:message for="name" errorClass="errors"/> 20. #{msgs.agePrompt}: 21. <h:inputText id="age" value="#{user.age}" required="true" 22. size="3" label="#{msgs.agePrompt}"/> 23. <h:message for="age" errorClass="errors"/> 24. </h:panelGrid> 25. <h:commandButton value="#{msgs.submitPrompt}"/> 26. </h:form> 27. </h:body> 28. </html>
Listing 4â&#x20AC;&#x201C;18 messages/src/java/com/corejsf/messages.properties 1. windowTitle=Using h:messages and h:message 2. greeting=Please fill out the following information 3. namePrompt=Name 4. agePrompt=Age 5. submitPrompt=Submit form
Listing 4â&#x20AC;&#x201C;19 messages/web/resources/css/styles.css 1. .errors { 2. font-style: italic; 3. color: red; 4. } 5. .emphasis { 6. font-size: 1.3em; 7. }
Conclusion You have now seen all HTML tags in the standard library with the exception of the tags used for tables, which are covered in Chapter 6. In the next chapter, you will learn how to use the Facelets tags.
177
CHAPTER 11
Performance and Scalability
BUY
from informIT and
SAVE 35%
Enter the coupon code JAVAONE10 during checkout.
Google Bookmarks
Delicious
Digg
StumbleUpon
Java Concurrency in Practice AUTHORS Brian Goetz, Tim Peierls, Joshua Bloch, Joseph Bowbeer, David Holmes, and Doug Lea
FORMATS & ISBNs Book: 9780321349606 eBook: 9780321467836 “ This is the book you need if you’re writing – or designing, or debugging, or maintaining, or contemplating--multithreaded Java programs. If you’ve ever had to synchronize a method and you weren’t sure why, you owe it to yourself and your users to read this book, cover to cover.” — MARTIN BUCHHOLZ JDK Concurrency Czar, Sun Microsystems
Threads are a fundamental part of the Java platform. As multicore processors become the norm, using concurrency effectively becomes essential for building highperformance applications. Java SE 5 and 6 are a huge step forward for the development of concurrent applications, with improvements to the Java Virtual Machine to support high-performance, highly scalable concurrent classes and a rich set of new concurrency building blocks. In Java Concurrency in Practice, the creators of these new facilities explain not only how they work and how to use them, but also the motivation and design patterns behind them.
However, developing, testing, and debugging multithreaded programs can still be very difficult; it is all too easy to create concurrent programs that appear to work, but fail when it matters most: in production, under heavy load. Java Concurrency in Practice arms readers with both the theoretical underpinnings and concrete techniques for building reliable, scalable, maintainable concurrent applications. Rather than simply offering an inventory of concurrency APIs and mechanisms, it provides design rules, patterns, and mental models that make it easier to build concurrent programs that are both correct and performant.
THIS BOOK COVERS: • Basic concepts of concurrency and thread safety • Techniques for building and composing thread-safe classes • Using the concurrency building blocks in java.util.concurrent • Performance optimization dos and don’ts • Testing concurrent programs • Advanced topics such as atomic variables, nonblocking algorithms, and the Java Memory Model
informit.com/learnjava
Chapter 11
Performance and Scalability One of the primary reasons to use threads is to improve performance.1 Using threads can improve resource utilization by letting applications more easily exploit available processing capacity, and can improve responsiveness by letting applications begin processing new tasks immediately while existing tasks are still running. This chapter explores techniques for analyzing, monitoring, and improving the performance of concurrent programs. Unfortunately, many of the techniques for improving performance also increase complexity, thus increasing the likelihood of safety and liveness failures. Worse, some techniques intended to improve performance are actually counterproductive or trade one sort of performance problem for another. While better performance is often desirable—and improving performance can be very satisfying—safety always comes first. First make your program right, then make it fast—and then only if your performance requirements and measurements tell you it needs to be faster. In designing a concurrent application, squeezing out the last bit of performance is often the least of your concerns.
11.1 Thinking about performance Improving performance means doing more work with fewer resources. The meaning of “resources” can vary; for a given activity, some specific resource is usually in shortest supply, whether it is CPU cycles, memory, network bandwidth, I/O bandwidth, database requests, disk space, or any number of other resources. When the performance of an activity is limited by availability of a particular resource, we say it is bound by that resource: CPU-bound, database-bound, etc. While the goal may be to improve performance overall, using multiple threads always introduces some performance costs compared to the single-threaded approach. These include the overhead associated with coordinating between threads (locking, signaling, and memory synchronization), increased context switching, 1. Some might argue this is the only reason we put up with the complexity threads introduce.
221
Chapter 11. Performance and Scalability
222
thread creation and teardown, and scheduling overhead. When threading is employed effectively, these costs are more than made up for by greater throughput, responsiveness, or capacity. On the other hand, a poorly designed concurrent application can perform even worse than a comparable sequential one.2 In using concurrency to achieve better performance, we are trying to do two things: utilize the processing resources we have more effectively, and enable our program to exploit additional processing resources if they become available. From a performance monitoring perspective, this means we are looking to keep the CPUs as busy as possible. (Of course, this doesn’t mean burning cycles with useless computation; we want to keep the CPUs busy with useful work.) If the program is compute-bound, then we may be able to increase its capacity by adding more processors; if it can’t even keep the processors we have busy, adding more won’t help. Threading offers a means to keep the CPU(s) “hotter” by decomposing the application so there is always work to be done by an available processor.
11.1.1
Performance versus scalability
Application performance can be measured in a number of ways, such as service time, latency, throughput, efficiency, scalability, or capacity. Some of these (service time, latency) are measures of “how fast” a given unit of work can be processed or acknowledged; others (capacity, throughput) are measures of “how much” work can be performed with a given quantity of computing resources. Scalability describes the ability to improve throughput or capacity when additional computing resources (such as additional CPUs, memory, storage, or I/O bandwidth) are added.
Designing and tuning concurrent applications for scalability can be very different from traditional performance optimization. When tuning for performance, the goal is usually to do the same work with less effort, such as by reusing previously computed results through caching or replacing an O(n2 ) algorithm with an O(n log n) one. When tuning for scalability, you are instead trying to find ways to parallelize the problem so you can take advantage of additional processing resources to do more work with more resources. These two aspects of performance—how fast and how much—are completely separate, and sometimes even at odds with each other. In order to achieve higher scalability or better hardware utilization, we often end up increasing the amount of work done to process each individual task, such as when we divide tasks into multiple “pipelined” subtasks. Ironically, many of the tricks that improve performance in single-threaded programs are bad for scalability (see Section 11.4.4 for an example). 2. A colleague provided this amusing anecodote: he had been involved in the testing of an expensive and complex application that managed its work via a tunable thread pool. After the system was complete, testing showed that the optimal number of threads for the pool was . . . 1. This should have been obvious from the outset; the target system was a single-CPU system and the application was almost entirely CPU-bound.
11.1. Thinking about performance
223
The familiar three-tier application model—in which presentation, business logic, and persistence are separated and may be handled by different systems— illustrates how improvements in scalability often come at the expense of performance. A monolithic application where presentation, business logic, and persistence are intertwined would almost certainly provide better performance for the first unit of work than would a well-factored multitier implementation distributed over multiple systems. How could it not? The monolithic application would not have the network latency inherent in handing off tasks between tiers, nor would it have to pay the costs inherent in separating a computational process into distinct abstracted layers (such as queuing overhead, coordination overhead, and data copying). However, when the monolithic system reaches its processing capacity, we could have a serious problem: it may be prohibitively difficult to significantly increase capacity. So we often accept the performance costs of longer service time or greater computing resources used per unit of work so that our application can scale to handle greater load by adding more resources. Of the various aspects of performance, the “how much” aspects—scalability, throughput, and capacity—are usually of greater concern for server applications than the “how fast” aspects. (For interactive applications, latency tends to be more important, so that users need not wait for indications of progress and wonder what is going on.) This chapter focuses primarily on scalability rather than raw single-threaded performance.
11.1.2
Evaluating performance tradeoffs
Nearly all engineering decisions involve some form of tradeoff. Using thicker steel in a bridge span may increase its capacity and safety, but also its construction cost. While software engineering decisions don’t usually involve tradeoffs between money and risk to human life, we often have less information with which to make the right tradeoffs. For example, the “quicksort” algorithm is highly efficient for large data sets, but the less sophisticated “bubble sort” is actually more efficient for small data sets. If you are asked to implement an efficient sort routine, you need to know something about the sizes of data sets it will have to process, along with metrics that tell you whether you are trying to optimize average-case time, worst-case time, or predictability. Unfortunately, that information is often not part of the requirements given to the author of a library sort routine. This is one of the reasons why most optimizations are premature: they are often undertaken before a clear set of requirements is available. Avoid premature optimization. First make it right, then make it fast—if it is not already fast enough.
When making engineering decisions, sometimes you are trading one form of cost for another (service time versus memory consumption); sometimes you are trading cost for safety. Safety doesn’t necessarily mean risk to human lives, as
Chapter 11. Performance and Scalability
224
it did in the bridge example. Many performance optimizations come at the cost of readability or maintainability—the more “clever” or nonobvious code is, the harder it is to understand and maintain. Sometimes optimizations entail compromising good object-oriented design principles, such as breaking encapsulation; sometimes they involve greater risk of error, because faster algorithms are usually more complicated. (If you can’t spot the costs or risks, you probably haven’t thought it through carefully enough to proceed.) Most performance decisions involve multiple variables and are highly situational. Before deciding that one approach is “faster” than another, ask yourself some questions: • What do you mean by “faster”? • Under what conditions will this approach actually be faster? Under light or heavy load? With large or small data sets? Can you support your answer with measurements? • How often are these conditions likely to arise in your situation? Can you support your answer with measurements? • Is this code likely to be used in other situations where the conditions may be different? • What hidden costs, such as increased development or maintenance risk, are you trading for this improved performance? Is this a good tradeoff? These considerations apply to any performance-related engineering decision, but this is a book about concurrency. Why are we recommending such a conservative approach to optimization? The quest for performance is probably the single greatest source of concurrency bugs. The belief that synchronization was “too slow” led to many clever-looking but dangerous idioms for reducing synchronization (such as double-checked locking, discussed in Section 16.2.4), and is often cited as an excuse for not following the rules regarding synchronization. Because concurrency bugs are among the most difficult to track down and eliminate, however, anything that risks introducing them must be undertaken very carefully. Worse, when you trade safety for performance, you may get neither. Especially when it comes to concurrency, the intuition of many developers about where a performance problem lies or which approach will be faster or more scalable is often incorrect. It is therefore imperative that any performance tuning exercise be accompanied by concrete performance requirements (so you know both when to tune and when to stop tuning) and with a measurement program in place using a realistic configuration and load profile. Measure again after tuning to verify that you’ve achieved the desired improvements. The safety and maintenance risks associated with many optimizations are bad enough—you don’t want to pay these costs if you don’t need to—and you definitely don’t want to pay them if you don’t even get the desired benefit. Measure, don’t guess.
11.2. Amdahl’s law
225
There are sophisticated profiling tools on the market for measuring performance and tracking down performance bottlenecks, but you don’t have to spend a lot of money to figure out what your program is doing. For example, the free perfbar application can give you a good picture of how busy the CPUs are, and since your goal is usually to keep the CPUs busy, this is a very good way to evaluate whether you need performance tuning or how effective your tuning has been.
11.2 Amdahl’s law Some problems can be solved faster with more resources—the more workers available for harvesting crops, the faster the harvest can be completed. Other tasks are fundamentally serial—no number of additional workers will make the crops grow any faster. If one of our primary reasons for using threads is to harness the power of multiple processors, we must also ensure that the problem is amenable to parallel decomposition and that our program effectively exploits this potential for parallelization. Most concurrent programs have a lot in common with farming, consisting of a mix of parallelizable and serial portions. Amdahl’s law describes how much a program can theoretically be sped up by additional computing resources, based on the proportion of parallelizable and serial components. If F is the fraction of the calculation that must be executed serially, then Amdahl’s law says that on a machine with N processors, we can achieve a speedup of at most: 1 (1 − F ) F+ N As N approaches infinity, the maximum speedup converges to 1/F, meaning that a program in which fifty percent of the processing must be executed serially can be sped up only by a factor of two, regardless of how many processors are available, and a program in which ten percent must be executed serially can be sped up by at most a factor of ten. Amdahl’s law also quantifies the efficiency cost of serialization. With ten processors, a program with 10% serialization can achieve at most a speedup of 5.3 (at 53% utilization), and with 100 processors it can achieve at most a speedup of 9.2 (at 9% utilization). It takes a lot of inefficiently utilized CPUs to never get to that factor of ten. Figure 11.1 shows the maximum possible processor utilization for varying degrees of serial execution and numbers of processors. (Utilization is defined as the speedup divided by the number of processors.) It is clear that as processor counts increase, even a small percentage of serialized execution limits how much throughput can be increased with additional computing resources. Chapter 6 explored identifying logical boundaries for decomposing applications into tasks. But in order to predict what kind of speedup is possible from running your application on a multiprocessor system, you also need to identify the sources of serialization in your tasks. Speedup ≤
Chapter 11. Performance and Scalability
226
1.0 0.8
0.25% 0.5%
0.6 Utilization
1%
0.4 0.2
5% 10% 30%
0.0 0
20
40 60 80 Number of processors
100
Figure 11.1. Maximum utilization under Amdahl’s law for various serialization percentages.
Imagine an application where N threads execute doWork in Listing 11.1, fetching tasks from a shared work queue and processing them; assume that tasks do not depend on the results or side effects of other tasks. Ignoring for a moment how the tasks get onto the queue, how well will this application scale as we add processors? At first glance, it may appear that the application is completely parallelizable: tasks do not wait for each other, and the more processors available, the more tasks can be processed concurrently. However, there is a serial component as well—fetching the task from the work queue. The work queue is shared by all the worker threads, and it will require some amount of synchronization to maintain its integrity in the face of concurrent access. If locking is used to guard the state of the queue, then while one thread is dequeing a task, other threads that need to dequeue their next task must wait—and this is where task processing is serialized. The processing time of a single task includes not only the time to execute the task Runnable, but also the time to dequeue the task from the shared work queue. If the work queue is a LinkedBlockingQueue, the dequeue operation may block less than with a synchronized LinkedList because LinkedBlockingQueue uses a more scalable algorithm, but accessing any shared data structure fundamentally introduces an element of serialization into a program. This example also ignores another common source of serialization: result handling. All useful computations produce some sort of result or side effect—if not, they can be eliminated as dead code. Since Runnable provides for no explicit result handling, these tasks must have some sort of side effect, say writing their results to a log file or putting them in a data structure. Log files and result containers are usually shared by multiple worker threads and therefore are also a
11.2. Amdahlâ&#x20AC;&#x2122;s law
227
public class WorkerThread extends Thread { private final BlockingQueue<Runnable> queue; public WorkerThread(BlockingQueue<Runnable> queue) { this.queue = queue; } public void run() { while (true) { try { Runnable task = queue.take(); task.run(); } catch (InterruptedException e) { break; /* Allow thread to exit */ } } } }
Listing 11.1. Serialized access to a task queue.
source of serialization. If instead each thread maintains its own data structure for results that are merged after all the tasks are performed, then the final merge is a source of serialization. All concurrent applications have some sources of serialization; if you think yours does not, look again.
11.2.1
Example: serialization hidden in frameworks
To see how serialization can be hidden in the structure of an application, we can compare throughput as threads are added and infer differences in serialization based on observed differences in scalability. Figure 11.2 shows a simple application in which multiple threads repeatedly remove an element from a shared Queue and process it, similar to Listing 11.1. The processing step involves only thread-local computation. If a thread finds the queue is empty, it puts a batch of new elements on the queue so that other threads have something to process on their next iteration. Accessing the shared queue clearly entails some degree of serialization, but the processing step is entirely parallelizable since it involves no shared data. The curves in Figure 11.2 compare throughput for two thread-safe Queue implementations: a LinkedList wrapped with synchronizedList, and a ConcurrentLinkedQueue. The tests were run on an 8-way Sparc V880 system running
Chapter 11. Performance and Scalability
228
6
ConcurrentLinkedQueue
4 Throughput (normalized) 2 synchronized LinkedList
0 1
4
8 12 Number of threads
16
Figure 11.2. Comparing queue implementations.
Solaris. While each run represents the same amount of “work”, we can see that merely changing queue implementations can have a big impact on scalability. The throughput of ConcurrentLinkedQueue continues to improve until it hits the number of processors and then remains mostly constant. On the other hand, the throughput of the synchronized LinkedList shows some improvement up to three threads, but then falls off as synchronization overhead increases. By the time it gets to four or five threads, contention is so heavy that every access to the queue lock is contended and throughput is dominated by context switching. The difference in throughput comes from differing degrees of serialization between the two queue implementations. The synchronized LinkedList guards the entire queue state with a single lock that is held for the duration of the offer or remove call; ConcurrentLinkedQueue uses a sophisticated nonblocking queue algorithm (see Section 15.4.2) that uses atomic references to update individual link pointers. In one, the entire insertion or removal is serialized; in the other, only updates to individual pointers are serialized.
11.2.2
Applying Amdahl’s law qualitatively
Amdahl’s law quantifies the possible speedup when more computing resources are available, if we can accurately estimate the fraction of execution that is serialized. Although measuring serialization directly can be difficult, Amdahl’s law can still be useful without such measurement. Since our mental models are influenced by our environment, many of us are used to thinking that a multiprocessor system has two or four processors, or maybe (if we’ve got a big budget) as many as a few dozen, because this is the technology that has been widely available in recent years. But as multicore CPUs
11.3. Costs introduced by threads
229
become mainstream, systems will have hundreds or even thousands of processors.3 Algorithms that seem scalable on a four-way system may have hidden scalability bottlenecks that have just not yet been encountered. When evaluating an algorithm, thinking “in the limit” about what would happen with hundreds or thousands of processors can offer some insight into where scaling limits might appear. For example, Sections 11.4.2 and 11.4.3 discuss two techniques for reducing lock granularity: lock splitting (splitting one lock into two) and lock striping (splitting one lock into many). Looking at them through the lens of Amdahl’s law, we see that splitting a lock in two does not get us very far towards exploiting many processors, but lock striping seems much more promising because the size of the stripe set can be increased as processor count increases. (Of course, performance optimizations should always be considered in light of actual performance requirements; in some cases, splitting a lock in two may be enough to meet the requirements.)
11.3 Costs introduced by threads Single-threaded programs incur neither scheduling nor synchronization overhead, and need not use locks to preserve the consistency of data structures. Scheduling and interthread coordination have performance costs; for threads to offer a performance improvement, the performance benefits of parallelization must outweigh the costs introduced by concurrency.
11.3.1
Context switching
If the main thread is the only schedulable thread, it will almost never be scheduled out. On the other hand, if there are more runnable threads than CPUs, eventually the OS will preempt one thread so that another can use the CPU. This causes a context switch, which requires saving the execution context of the currently running thread and restoring the execution context of the newly scheduled thread. Context switches are not free; thread scheduling requires manipulating shared data structures in the OS and JVM. The OS and JVM use the same CPUs your program does; more CPU time spent in JVM and OS code means less is available for your program. But OS and JVM activity is not the only cost of context switches. When a new thread is switched in, the data it needs is unlikely to be in the local processor cache, so a context switch causes a flurry of cache misses, and thus threads run a little more slowly when they are first scheduled. This is one of the reasons that schedulers give each runnable thread a certain minimum time quantum even when many other threads are waiting: it amortizes the cost of the context switch and its consequences over more uninterrupted execution time, improving overall throughput (at some cost to responsiveness). 3. Market update: at this writing, Sun is shipping low-end server systems based on the 8-core Niagara processor, and Azul is shipping high-end server systems (96, 192, and 384-way) based on the 24-core Vega processor.
Chapter 11. Performance and Scalability
230
synchronized (new Object()) { // do something }
Listing 11.2. Synchronization that has no effect. Don’t do this.
When a thread blocks because it is waiting for a contended lock, the JVM usually suspends the thread and allows it to be switched out. If threads block frequently, they will be unable to use their full scheduling quantum. A program that does more blocking (blocking I/O, waiting for contended locks, or waiting on condition variables) incurs more context switches than one that is CPU-bound, increasing scheduling overhead and reducing throughput. (Nonblocking algorithms can also help reduce context switches; see Chapter 15.) The actual cost of context switching varies across platforms, but a good rule of thumb is that a context switch costs the equivalent of 5,000 to 10,000 clock cycles, or several microseconds on most current processors. The vmstat command on Unix systems and the perfmon tool on Windows systems report the number of context switches and the percentage of time spent in the kernel. High kernel usage (over 10%) often indicates heavy scheduling activity, which may be caused by blocking due to I/O or lock contention.
11.3.2
Memory synchronization
The performance cost of synchronization comes from several sources. The visibility guarantees provided by synchronized and volatile may entail using special instructions called memory barriers that can flush or invalidate caches, flush hardware write buffers, and stall execution pipelines. Memory barriers may also have indirect performance consequences because they inhibit other compiler optimizations; most operations cannot be reordered with memory barriers. When assessing the performance impact of synchronization, it is important to distinguish between contended and uncontended synchronization. The synchronized mechanism is optimized for the uncontended case (volatile is always uncontended), and at this writing, the performance cost of a “fast-path” uncontended synchronization ranges from 20 to 250 clock cycles for most systems. While this is certainly not zero, the effect of needed, uncontended synchronization is rarely significant in overall application performance, and the alternative involves compromising safety and potentially signing yourself (or your successor) up for some very painful bug hunting later. Modern JVMs can reduce the cost of incidental synchronization by optimizing away locking that can be proven never to contend. If a lock object is accessible only to the current thread, the JVM is permitted to optimize away a lock acquisition because there is no way another thread could synchronize on the same lock. For example, the lock acquisition in Listing 11.2 can always be eliminated by the JVM. More sophisticated JVMs can use escape analysis to identify when a local object reference is never published to the heap and is therefore thread-local. In get-
11.3. Costs introduced by threads
231
StoogeNames in Listing 11.3, the only reference to the List is the local variable stooges , and stack-confined variables are automatically thread-local. A naive execution of getStoogeNames would acquire and release the lock on the Vector four times, once for each call to add or toString. However, a smart runtime compiler can inline these calls and then see that stooges and its internal state
never escape, and therefore that all four lock acquisitions can be eliminated.4 public String getStoogeNames() { List<String> stooges = new Vector<String>(); stooges.add("Moe"); stooges.add("Larry"); stooges.add("Curly"); return stooges.toString(); }
Listing 11.3. Candidate for lock elision. Even without escape analysis, compilers can also perform lock coarsening, the merging of adjacent synchronized blocks using the same lock. For getStoogeNames, a JVM that performs lock coarsening might combine the three calls to add and the call to toString into a single lock acquisition and release, using heuristics on the relative cost of synchronization versus the instructions inside the synchronized block.5 Not only does this reduce the synchronization overhead, but it also gives the optimizer a much larger block to work with, likely enabling other optimizations. Donâ&#x20AC;&#x2122;t worry excessively about the cost of uncontended synchronization. The basic mechanism is already quite fast, and JVMs can perform additional optimizations that further reduce or eliminate the cost. Instead, focus optimization eďŹ&#x20AC;orts on areas where lock contention actually occurs.
Synchronization by one thread can also affect the performance of other threads. Synchronization creates traffic on the shared memory bus; this bus has a limited bandwidth and is shared across all processors. If threads must compete for synchronization bandwidth, all threads using synchronization will suffer.6 4. This compiler optimization, called lock elision, is performed by the IBM JVM and is expected in HotSpot as of Java 7. 5. A smart dynamic compiler can figure out that this method always returns the same string, and after the first execution recompile getStoogeNames to simply return the value returned by the first execution. 6. This aspect is sometimes used to argue against the use of nonblocking algorithms without some sort of backoff, because under heavy contention, nonblocking algorithms generate more synchronization traffic than lock-based ones. See Chapter 15.
Chapter 11. Performance and Scalability
232
11.3.3
Blocking
Uncontended synchronization can be handled entirely within the JVM (Bacon et al., 1998); contended synchronization may require OS activity, which adds to the cost. When locking is contended, the losing thread(s) must block. The JVM can implement blocking either via spin-waiting (repeatedly trying to acquire the lock until it succeeds) or by suspending the blocked thread through the operating system. Which is more efficient depends on the relationship between context switch overhead and the time until the lock becomes available; spin-waiting is preferable for short waits and suspension is preferable for long waits. Some JVMs choose between the two adaptively based on profiling data of past wait times, but most just suspend threads waiting for a lock. Suspending a thread because it could not get a lock, or because it blocked on a condition wait or blocking I/O operation, entails two additional context switches and all the attendant OS and cache activity: the blocked thread is switched out before its quantum has expired, and is then switched back in later after the lock or other resource becomes available. (Blocking due to lock contention also has a cost for the thread holding the lock: when it releases the lock, it must then ask the OS to resume the blocked thread.)
11.4 Reducing lock contention We’ve seen that serialization hurts scalability and that context switches hurt performance. Contended locking causes both, so reducing lock contention can improve both performance and scalability. Access to resources guarded by an exclusive lock is serialized—only one thread at a time may access it. Of course, we use locks for good reasons, such as preventing data corruption, but this safety comes at a price. Persistent contention for a lock limits scalability. The principal threat to scalability in concurrent applications is the exclusive resource lock.
Two factors influence the likelihood of contention for a lock: how often that lock is requested and how long it is held once acquired.7 If the product of these factors is sufficiently small, then most attempts to acquire the lock will be uncontended, and lock contention will not pose a significant scalability impediment. If, however, the lock is in sufficiently high demand, threads will block waiting for it; in the extreme case, processors will sit idle even though there is plenty of work to do. 7. This is a corollary of Little’s law, a result from queueing theory that says “the average number of customers in a stable system is equal to their average arrival rate multiplied by their average time in the system”. (Little, 1961)
11.4. Reducing lock contention
233
There are three ways to reduce lock contention: • Reduce the duration for which locks are held; • Reduce the frequency with which locks are requested; or • Replace exclusive locks with coordination mechanisms that permit greater concurrency.
11.4.1
Narrowing lock scope (“Get in, get out”)
An effective way to reduce the likelihood of contention is to hold locks as briefly as possible. This can be done by moving code that doesn’t require the lock out of synchronized blocks, especially for expensive operations and potentially blocking operations such as I/O. It is easy to see how holding a “hot” lock for too long can limit scalability; we saw an example of this in SynchronizedFactorizer in Chapter 2. If an operation holds a lock for 2 milliseconds and every operation requires that lock, throughput can be no greater than 500 operations per second, no matter how many processors are available. Reducing the time the lock is held to 1 millisecond improves the lock-induced throughput limit to 1000 operations per second.8 AttributeStore in Listing 11.4 shows an example of holding a lock longer than necessary. The userLocationMatches method looks up the user’s location in a Map and uses regular expression matching to see if the resulting value matches the supplied pattern. The entire userLocationMatches method is synchronized, but the only portion of the code that actually needs the lock is the call to Map.get. @ThreadSafe public class AttributeStore { @GuardedBy("this") private final Map<String, String> attributes = new HashMap<String, String>(); public synchronized boolean userLocationMatches(String name, String regexp) { String key = "users." + name + ".location"; String location = attributes.get(key); if (location == null) return false; else return Pattern.matches(regexp, location); } }
Listing 11.4. Holding a lock longer than necessary. 8. Actually, this calculation understates the cost of holding locks for too long because it doesn’t take into account the context switch overhead generated by increased lock contention.
234
Chapter 11. Performance and Scalability
BetterAttributeStore in Listing 11.5 rewrites AttributeStore to reduce significantly the lock duration. The first step is to construct the Map key associated with the user’s location, a string of the form users.name.location. This entails instantiating a StringBuilder object, appending several strings to it, and instantiating the result as a String . After the location has been retrieved, the regular expression is matched against the resulting location string. Because constructing the key string and processing the regular expression do not access shared state, they need not be executed with the lock held. BetterAttributeStore factors these steps out of the synchronized block, thus reducing the time the lock is held. @ThreadSafe public class BetterAttributeStore { @GuardedBy("this") private final Map<String, String> attributes = new HashMap<String, String>(); public boolean userLocationMatches(String name, String regexp) { String key = "users." + name + ".location"; String location; synchronized (this) { location = attributes.get(key); } if (location == null) return false; else return Pattern.matches(regexp, location); } }
Listing 11.5. Reducing lock duration.
Reducing the scope of the lock in userLocationMatches substantially reduces the number of instructions that are executed with the lock held. By Amdahl’s law, this removes an impediment to scalability because the amount of serialized code is reduced. Because AttributeStore has only one state variable, attributes, we can improve it further by the technique of delegating thread safety (Section 4.3). By replacing attributes with a thread-safe Map (a Hashtable, synchronizedMap, or ConcurrentHashMap), AttributeStore can delegate all its thread safety obligations to the underlying thread-safe collection. This eliminates the need for explicit synchronization in AttributeStore, reduces the lock scope to the duration of the Map access, and removes the risk that a future maintainer will undermine thread safety by forgetting to acquire the appropriate lock before accessing attributes. While shrinking synchronized blocks can improve scalability, a synchronized block can be too small—operations that need to be atomic (such updating multiple variables that participate in an invariant) must be contained in a single synchro-
11.4. Reducing lock contention
235
nized block. And because the cost of synchronization is nonzero, breaking one synchronized block into multiple synchronized blocks (correctness permitting)
at some point becomes counterproductive in terms of performance.9 The ideal balance is of course platform-dependent, but in practice it makes sense to worry about the size of a synchronized block only when you can move “substantial” computation or blocking operations out of it.
11.4.2
Reducing lock granularity
The other way to reduce the fraction of time that a lock is held (and therefore the likelihood that it will be contended) is to have threads ask for it less often. This can be accomplished by lock splitting and lock striping, which involve using separate locks to guard multiple independent state variables previously guarded by a single lock. These techniques reduce the granularity at which locking occurs, potentially allowing greater scalability—but using more locks also increases the risk of deadlock. As a thought experiment, imagine what would happen if there was only one lock for the entire application instead of a separate lock for each object. Then execution of all synchronized blocks, regardless of their lock, would be serialized. With many threads competing for the global lock, the chance that two threads want the lock at the same time increases, resulting in more contention. So if lock requests were instead distributed over a larger set of locks, there would be less contention. Fewer threads would be blocked waiting for locks, thus increasing scalability. If a lock guards more than one independent state variable, you may be able to improve scalability by splitting it into multiple locks that each guard different variables. This results in each lock being requested less often. ServerStatus in Listing 11.6 shows a portion of the monitoring interface for a database server that maintains the set of currently logged-on users and the set of currently executing queries. As a user logs on or off or query execution begins or ends, the ServerStatus object is updated by calling the appropriate add or remove method. The two types of information are completely independent; ServerStatus could even be split into two separate classes with no loss of functionality. Instead of guarding both users and queries with the ServerStatus lock, we can instead guard each with a separate lock, as shown in Listing 11.7. After splitting the lock, each new finer-grained lock will see less locking traffic than the original coarser lock would have. (Delegating to a thread-safe Set implementation for users and queries instead of using explicit synchronization would implicitly provide lock splitting, as each Set would use a different lock to guard its state.) Splitting a lock into two offers the greatest possibility for improvement when the lock is experiencing moderate but not heavy contention. Splitting locks that are experiencing little contention yields little net improvement in performance or throughput, although it might increase the load threshold at which performance starts to degrade due to contention. Splitting locks experiencing moderate con9. If the JVM performs lock coarsening, it may undo the splitting of synchronized blocks anyway.
236
Chapter 11. Performance and Scalability
@ThreadSafe public class ServerStatus { @GuardedBy("this") public final Set<String> users; @GuardedBy("this") public final Set<String> queries; ... public synchronized void addUser(String u) { users.add(u); } public synchronized void addQuery(String q) { queries.add(q); } public synchronized void removeUser(String u) { users.remove(u); } public synchronized void removeQuery(String q) { queries.remove(q); } }
Listing 11.6. Candidate for lock splitting.
@ThreadSafe public class ServerStatus { @GuardedBy("users") public final Set<String> users; @GuardedBy("queries") public final Set<String> queries; ... public void addUser(String u) { synchronized (users) { users.add(u); } } public void addQuery(String q) { synchronized (queries) { queries.add(q); } } // remove methods similarly refactored to use split locks }
Listing 11.7. ServerStatus refactored to use split locks.
11.4. Reducing lock contention
237
tention might actually turn them into mostly uncontended locks, which is the most desirable outcome for both performance and scalability.
11.4.3
Lock striping
Splitting a heavily contended lock into two is likely to result in two heavily contended locks. While this will produce a small scalability improvement by enabling two threads to execute concurrently instead of one, it still does not dramatically improve prospects for concurrency on a system with many processors. The lock splitting example in the ServerStatus classes does not offer any obvious opportunity for splitting the locks further. Lock splitting can sometimes be extended to partition locking on a variablesized set of independent objects, in which case it is called lock striping. For example, the implementation of ConcurrentHashMap uses an array of 16 locks, each of which guards 1/16 of the hash buckets; bucket N is guarded by lock N mod 16. Assuming the hash function provides reasonable spreading characteristics and keys are accessed uniformly, this should reduce the demand for any given lock by approximately a factor of 16. It is this technique that enables ConcurrentHashMap to support up to 16 concurrent writers. (The number of locks could be increased to provide even better concurrency under heavy access on high-processor-count systems, but the number of stripes should be increased beyond the default of 16 only when you have evidence that concurrent writers are generating enough contention to warrant raising the limit.) One of the downsides of lock striping is that locking the collection for exclusive access is more difficult and costly than with a single lock. Usually an operation can be performed by acquiring at most one lock, but occasionally you need to lock the entire collection, as when ConcurrentHashMap needs to expand the map and rehash the values into a larger set of buckets. This is typically done by acquiring all of the locks in the stripe set.10 StripedMap in Listing 11.8 illustrates implementing a hash-based map using lock striping. There are N_LOCKS locks, each guarding a subset of the buckets. Most methods, like get, need acquire only a single bucket lock. Some methods may need to acquire all the locks but, as in the implementation for clear, may not need to acquire them all simultaneously.11
11.4.4
Avoiding hot fields
Lock splitting and lock striping can improve scalability because they enable different threads to operate on different data (or different portions of the same data structure) without interfering with each other. A program that would benefit from lock splitting necessarily exhibits contention for a lock more often than for the data 10. The only way to acquire an arbitrary set of intrinsic locks is via recursion. 11. Clearing the Map in this way is not atomic, so there is not necessarily a time when the StripedMap is actually empty if other threads are concurrently adding elements; making the operation atomic would require acquiring all the locks at once. However, for concurrent collections that clients typically cannot lock for exclusive access, the result of methods like size or isEmpty may be out of date by the time they return anyway, so this behavior, while perhaps somewhat surprising, is usually acceptable.
Chapter 11. Performance and Scalability
238
@ThreadSafe public class StripedMap { // Synchronization policy: buckets[n] guarded by locks[n%N_LOCKS] private static final int N_LOCKS = 16; private final Node[] buckets; private final Object[] locks; private static class Node { ... } public StripedMap(int numBuckets) { buckets = new Node[numBuckets]; locks = new Object[N_LOCKS]; for (int i = 0; i < N_LOCKS; i++) locks[i] = new Object(); } private final int hash(Object key) { return Math.abs(key.hashCode() % buckets.length); } public Object get(Object key) { int hash = hash(key); synchronized (locks[hash % N_LOCKS]) { for (Node m = buckets[hash]; m != null; m = m.next) if (m.key.equals(key)) return m.value; } return null; } public void clear() { for (int i = 0; i < buckets.length; i++) { synchronized (locks[i % N_LOCKS]) { buckets[i] = null; } } } ... }
Listing 11.8. Hash-based map using lock striping.
11.4. Reducing lock contention
239
guarded by that lock. If a lock guards two independent variables X and Y, and thread A wants to access X while B wants to access Y (as would be the case if one thread called addUser while another called addQuery in ServerStatus), then the two threads are not contending for any data, even though they are contending for a lock. Lock granularity cannot be reduced when there are variables that are required for every operation. This is yet another area where raw performance and scalability are often at odds with each other; common optimizations such as caching frequently computed values can introduce “hot fields” that limit scalability. If you were implementing HashMap, you would have a choice of how size computes the number of entries in the Map. The simplest approach is to count the number of entries every time it is called. A common optimization is to update a separate counter as entries are added or removed; this slightly increases the cost of a put or remove operation to keep the counter up-to-date, but reduces the cost of the size operation from O(n) to O(1). Keeping a separate count to speed up operations like size and isEmpty works fine for a single-threaded or fully synchronized implementation, but makes it much harder to improve the scalability of the implementation because every operation that modifies the map must now update the shared counter. Even if you use lock striping for the hash chains, synchronizing access to the counter reintroduces the scalability problems of exclusive locking. What looked like a performance optimization—caching the results of the size operation—has turned into a scalability liability. In this case, the counter is called a hot field because every mutative operation needs to access it. ConcurrentHashMap avoids this problem by having size enumerate the stripes and add up the number of elements in each stripe, instead of maintaining a global count. To avoid enumerating every element, ConcurrentHashMap maintains a separate count field for each stripe, also guarded by the stripe lock.12
11.4.5
Alternatives to exclusive locks
A third technique for mitigating the effect of lock contention is to forego the use of exclusive locks in favor of a more concurrency-friendly means of managing shared state. These include using the concurrent collections, read-write locks, immutable objects and atomic variables. ReadWriteLock (see Chapter 13) enforces a multiple-reader, single-writer locking discipline: more than one reader can access the shared resource concurrently so long as none of them wants to modify it, but writers must acquire the lock excusively. For read-mostly data structures, ReadWriteLock can offer greater concurrency than exclusive locking; for read-only data structures, immutability can eliminate the need for locking entirely. Atomic variables (see Chapter 15) offer a means of reducing the cost of updating “hot fields” such as statistics counters, sequence generators, or the reference 12. If size is called frequently compared to mutative operations, striped data structures can optimize for this by caching the collection size in a volatile whenever size is called and invalidating the cache (setting it to -1) whenever the collection is modified. If the cached value is nonnegative on entry to size, it is accurate and can be returned; otherwise it is recomputed.
Chapter 11. Performance and Scalability
240
to the first node in a linked data structure. (We used AtomicLong to maintain the hit counter in the servlet examples in Chapter 2.) The atomic variable classes provide very fine-grained (and therefore more scalable) atomic operations on integers or object references, and are implemented using low-level concurrency primitives (such as compare-and-swap) provided by most modern processors. If your class has a small number of hot fields that do not participate in invariants with other variables, replacing them with atomic variables may improve scalability. (Changing your algorithm to have fewer hot fields might improve scalability even more— atomic variables reduce the cost of updating hot fields, but they don’t eliminate it.)
11.4.6
Monitoring CPU utilization
When testing for scalability, the goal is usually to keep the processors fully utilized. Tools like vmstat and mpstat on Unix systems or perfmon on Windows systems can tell you just how “hot” the processors are running. If the CPUs are asymmetrically utilized (some CPUs are running hot but others are not) your first goal should be to find increased parallelism in your program. Asymmetric utilization indicates that most of the computation is going on in a small set of threads, and your application will not be able to take advantage of additional processors. If the CPUs are not fully utilized, you need to figure out why. There are several likely causes: Insufficent load. It may be that the application being tested is just not subjected to enough load. You can test for this by increasing the load and measuring changes in utilization, response time, or service time. Generating enough load to saturate an application can require substantial computer power; the problem may be that the client systems, not the system being tested, are running at capacity. I/O-bound. You can determine whether an application is disk-bound using iostat or perfmon , and whether it is bandwidth-limited by monitoring traffic levels on your network. Externally bound. If your application depends on external services such as a database or web service, the bottleneck may not be in your code. You can test for this by using a profiler or database administration tools to determine how much time is being spent waiting for answers from the external service. Lock contention. Profiling tools can tell you how much lock contention your application is experiencing and which locks are “hot”. You can often get the same information without a profiler through random sampling, triggering a few thread dumps and looking for threads contending for locks. If a thread is blocked waiting for a lock, the appropriate stack frame in the thread dump indicates “waiting to lock monitor . . . ” Locks that are mostly uncontended rarely show up in a thread dump; a heavily contended lock will almost always have at least one thread waiting to acquire it and so will frequently appear in thread dumps.
11.4. Reducing lock contention
241
If your application is keeping the CPUs sufficiently hot, you can use monitoring tools to infer whether it would benefit from additional CPUs. A program with only four threads may be able to keep a 4-way system fully utilized, but is unlikely to see a performance boost if moved to an 8-way system since there would need to be waiting runnable threads to take advantage of the additional processors. (You may also be able to reconfigure the program to divide its workload over more threads, such as adjusting a thread pool size.) One of the columns reported by vmstat is the number of threads that are runnable but not currently running because a CPU is not available; if CPU utilization is high and there are always runnable threads waiting for a CPU, your application would probably benefit from more processors.
11.4.7
Just say no to object pooling
In early JVM versions, object allocation and garbage collection were slow,13 but their performance has improved substantially since then. In fact, allocation in Java is now faster than malloc is in C: the common code path for new Object in HotSpot 1.4.x and 5.0 is approximately ten machine instructions. To work around “slow” object lifecycles, many developers turned to object pooling, where objects are recycled instead of being garbage collected and allocated anew when needed. Even taking into account its reduced garbage collection overhead, object pooling has been shown to be a performance loss14 for all but the most expensive objects (and a serious loss for light- and medium-weight objects) in single-threaded programs (Click, 2005). In concurrent applications, pooling fares even worse. When threads allocate new objects, very little inter-thread coordination is required, as allocators typically use thread-local allocation blocks to eliminate most synchronization on heap data structures. But if those threads instead request an object from a pool, some synchronization is necessary to coordinate access to the pool data structure, creating the possibility that a thread will block. Because blocking a thread due to lock contention is hundreds of times more expensive than an allocation, even a small amount of pool-induced contention would be a scalability bottleneck. (Even an uncontended synchronization is usually more expensive than allocating an object.) This is yet another technique intended as a performance optimization but that turned into a scalability hazard. Pooling has its uses,15 but is of limited utility as a performance optimization. 13. As was everything else—synchronization, graphics, JVM startup, reflection—predictably so in the first version of an experimental technology. 14. In addition to being a loss in terms of CPU cycles, object pooling has a number of other problems, among them the challenge of setting pool sizes correctly (too small, and pooling has no effect; too large, and it puts pressure on the garbage collector, retaining memory that could be used more effectively for something else); the risk that an object will not be properly reset to its newly allocated state, introducing subtle bugs; the risk that a thread will return an object to the pool but continue using it; and that it makes more work for generational garbage collectors by encouraging a pattern of old-to-young references. 15. In constrained environments, such as some J2ME or RTSJ targets, object pooling may still be required for effective memory management or to manage responsiveness.
242
Chapter 11. Performance and Scalability
Allocating objects is usually cheaper than synchronizing.
11.5 Example: Comparing Map performance The single-threaded performance of ConcurrentHashMap is slightly better than that of a synchronized HashMap, but it is in concurrent use that it really shines. The implementation of ConcurrentHashMap assumes the most common operation is retrieving a value that already exists, and is therefore optimized to provide highest performance and concurrency for successful get operations. The major scalability impediment for the synchronized Map implementations is that there is a single lock for the entire map, so only one thread can access the map at a time. On the other hand, ConcurrentHashMap does no locking for most successful read operations, and uses lock striping for write operations and those few read operations that do require locking. As a result, multiple threads can access the Map concurrently without blocking. Figure 11.3 illustrates the differences in scalability between several Map implementations: ConcurrentHashMap, ConcurrentSkipListMap, and HashMap and TreeMap wrapped with synchronizedMap. The first two are thread-safe by design; the latter two are made thread-safe by the synchronized wrapper. In each run, N threads concurrently execute a tight loop that selects a random key and attempts to retrieve the value corresponding to that key. If the value is not present, it is added to the Map with probability p = .6, and if it is present, is removed with probability p = .02. The tests were run under a pre-release build of Java 6 on an 8-way Sparc V880, and the graph displays throughput normalized to the onethread case for ConcurrentHashMap. (The scalability gap between the concurrent and synchronized collections is even larger on Java 5.0.) The data for ConcurrentHashMap and ConcurrentSkipListMap shows that they scale well to large numbers of threads; throughput continues to improve as threads are added. While the numbers of threads in Figure 11.3 may not seem large, this test program generates more contention per thread than a typical application because it does little other than pound on the Map; a real program would do additional thread-local work in each iteration. The numbers for the synchronized collections are not as encouraging. Performance for the one-thread case is comparable to ConcurrentHashMap, but once the load transitions from mostly uncontended to mostly contendedâ&#x20AC;&#x201D;which happens here at two threadsâ&#x20AC;&#x201D;the synchronized collections suffer badly. This is common behavior for code whose scalability is limited by lock contention. So long as contention is low, time per operation is dominated by the time to actually do the work and throughput may improve as threads are added. Once contention becomes significant, time per operation is dominated by context switch and scheduling delays, and adding more threads has little effect on throughput.
11.6. Reducing context switch overhead
243
4
Throughput (normalized)
3
ConcurrentHashMap
2
ConcurrentSkipListMap
1 synchronized HashMap synchronized TreeMap
0 1
2
4 8 16 Number of threads
32
64
Figure 11.3. Comparing scalability of Map implementations.
11.6 Reducing context switch overhead Many tasks involve operations that may block; transitioning between the running and blocked states entails a context switch. One source of blocking in server applications is generating log messages in the course of processing requests; to illustrate how throughput can be improved by reducing context switches, weâ&#x20AC;&#x2122;ll analyze the scheduling behavior of two logging approaches. Most logging frameworks are thin wrappers around println ; when you have something to log, just write it out right then and there. Another approach was shown in LogWriter on page 152: the logging is performed in a dedicated background thread instead of by the requesting thread. From the developerâ&#x20AC;&#x2122;s perspective, both approaches are roughly equivalent. But there may be a difference in performance, depending on the volume of logging activity, how many threads are doing logging, and other factors such as the cost of context switching.16 The service time for a logging operation includes whatever computation is associated with the I/O stream classes; if the I/O operation blocks, it also includes the duration for which the thread is blocked. The operating system will deschedule the blocked thread until the I/O completes, and probably a little longer. When the I/O completes, other threads are probably active and will be allowed to finish out their scheduling quanta, and threads may already be waiting ahead of us on 16. Building a logger that moves the I/O to another thread may improve performance, but it also introduces a number of design complications, such as interruption (what happens if a thread blocked in a logging operation is interrupted?), service guarantees (does the logger guarantee that a successfully queued log message will be logged prior to service shutdown?), saturation policy (what happens when the producers log messages faster than the logger thread can handle them?), and service lifecycle (how do we shut down the logger, and how do we communicate the service state to producers?).
244
Chapter 11. Performance and Scalability
the scheduling queue—further adding to service time. Alternatively, if multiple threads are logging simultaneously, there may be contention for the output stream lock, in which case the result is the same as with blocking I/O—the thread blocks waiting for the lock and gets switched out. Inline logging involves I/O and locking, which can lead to increased context switching and therefore increased service times. Increasing request service time is undesirable for several reasons. First, service time affects quality of service: longer service times mean someone is waiting longer for a result. But more significantly, longer service times in this case mean more lock contention. The “get in, get out” principle of Section 11.4.1 tells us that we should hold locks as briefly as possible, because the longer a lock is held, the more likely that lock will be contended. If a thread blocks waiting for I/O while holding a lock, another thread is more likely to want the lock while the first thread is holding it. Concurrent systems perform much better when most lock acquisitions are uncontended, because contended lock acquisition means more context switches. A coding style that encourages more context switches thus yields lower overall throughput. Moving the I/O out of the request-processing thread is likely to shorten the mean service time for request processing. Threads calling log no longer block waiting for the output stream lock or for I/O to complete; they need only queue the message and can then return to their task. On the other hand, we’ve introduced the possibility of contention for the message queue, but the put operation is lighter-weight than the logging I/O (which might require system calls) and so is less likely to block in actual use (as long as the queue is not full). Because the request thread is now less likely to block, it is less likely to be context-switched out in the middle of a request. What we’ve done is turned a complicated and uncertain code path involving I/O and possible lock contention into a straight-line code path. To some extent, we are just moving the work around, moving the I/O to a thread where its cost isn’t perceived by the user (which may in itself be a win). But by moving all the logging I/O to a single thread, we also eliminate the chance of contention for the output stream and thus eliminate a source of blocking. This improves overall throughput because fewer resources are consumed in scheduling, context switching, and lock management. Moving the I/O from many request-processing threads to a single logger thread is similar to the difference between a bucket brigade and a collection of individuals fighting a fire. In the “hundred guys running around with buckets” approach, you have a greater chance of contention at the water source and at the fire (resulting in overall less water delivered to the fire), plus greater inefficiency because each worker is continuously switching modes (filling, running, dumping, running, etc.). In the bucket-brigade approach, the flow of water from the source to the burning building is constant, less energy is expended transporting the water to the fire, and each worker focuses on doing one job continuously. Just as interruptions are disruptive and productivity-reducing to humans, blocking and context switching are disruptive to threads.
11.6. Reducing context switch overhead
245
Summary Because one of the most common reasons to use threads is to exploit multiple processors, in discussing the performance of concurrent applications, we are usually more concerned with throughput or scalability than we are with raw service time. Amdahlâ&#x20AC;&#x2122;s law tells us that the scalability of an application is driven by the proportion of code that must be executed serially. Since the primary source of serialization in Java programs is the exclusive resource lock, scalability can often be improved by spending less time holding locks, either by reducing lock granularity, reducing the duration for which locks are held, or replacing exclusive locks with nonexclusive or nonblocking alternatives.
CHAPTER 27
Using External Data Sources
DRAFT MANUSCRIPT PUBLISHING SEPTEMBER 2010
BUY
from informIT and
SAVE 35%
Enter the coupon code JAVAONE10 during checkout.
Google Bookmarks
Delicious
Digg
StumbleUpon
JavaFX Developer’s Guide AUTHOR Kim Topley
FORMATS & ISBNs Book: 9780321601650 eBook: 9780321648938 Safari Books Online: 9780321648983
JavaFX™ Developer’s Guide is the definitive guide to the powerful new JavaFX scripting language, which allows development of rich Internet applications with Flash-like animation. Kim Topley, best-selling author of Core JFC and Core Swing, is one of the world’s leading experts on building rich, visual applications with Java technologies. Now, he’s written the industry’s broadest, deepest, and most useful guide to JavaFX for experienced Java programmers. Topley highlights critical topics that other books gloss over, presents detailed examples that
stretch JavaFX to its limits, and shows you exactly how to build on the skills you already have. All code examples are available for download and are based on official JavaFX releases, not betas. You’ll find complete coverage of • JavaFX basics • Data types • Expressions, functions, and object literals • Language statements • Binding and triggers • Creating classes • Reflection • Images and filters • Transformations • Animation
• User interface basics, attributes, events, and panels • Dialogs, windows, and internal frames • JavaFX and XML • JavaFX development with NetBeans and Ant • Packaging and deployment This book is an invaluable resource for Java developers who want to build rich Internet applications for Web, mobile, and desktop environments. Whether you’ve been focused on HTML/XML/CSS Web development or Java Swing, Topley will help you get outstanding results with JavaFX.
informit.com/learnjava
27 Using External Data Sources T he essence of a rich Internet application is its ability to display and manipulate data from the Internet, a corporate network, or even on your home network. This chapter looks at the facilities in the JavaFX platform that enable you to access external data sources. We start with the HttpRequest class, which provides a simple way to read data from and write data to a web server. External data access is inherently slow, so it is essential to ensure that it does not block the application thread that is responsible for updating the user interface. The HttpRequest class takes care of this for you by transparently doing its job in the background, while delivering its results in the user interface thread. The second part of this chapter introduces the PullParser class, which allows you to parse a data stream encoded either in XML (Extensible Markup Language) or in JSON (JavaScript Object Notation). XML and JSON are commonly used to encode information returned by RESTful web services, and you see several examples that demonstrate how easy it is to use the HttpRequest class together with the PullParser to retrieve data from a web service, specifically an XML-based web service provided by Twitter and a weather service that uses the JSON format. At a slightly higher level of abstraction, the JavaFX platform can fetch or subscribe to the content of either an RSS or an Atom feed and parse it into JavaFX objects that are easier for an application to use than raw XML or JSON. In the third part of this chapter, we use the feed application programming interfaces (APIs) to create an application that displays information from the Yahoo! weather service and another that lets you search postings on the Twitter website. This chapter closes with a discussion of the classes in the javafx.async package, which allow you to safely delegate to a background thread tasks that would otherwise block the user interface for an unacceptable period of time. We illustrate the general technique by showing you how to write an application that reads records from a database and uses the results to populate a ListView control.
The classes used in this chapter are all part of the common profile, but some of the example code in this chapter uses APIs that are available only in the desktop profile, so you will find all the example source code in the javafxdata package of the JavaFX Book Desktop project.
The HttpRequest Class In this section, we look at the classes in the javafx.io.http package that let you send a request to a web server using the HTTP (or HTTPS) protocol and receive the data that is returned. The principal class in this package, HttpRequest, is quite complex, but in return for the complexity you get a lot of functionality. In this section, you see how to use the HttpRequest class to connect to a web server and receive notification as data is read from it or written to it. In the sections that follow, we go a step further by using the HttpRequest class along with the XML parsing classes in the javafx.data.pull package to invoke a web service and interpret the results. It is worth noting that, despite its name, HttpRequest is not limited to the HTTP protocol. As you'll see later in this chapter, it can, in fact, retrieve data from any URL that is supported by the Java platform on which it is running.
Basic Use of the HttpRequest Class The simplest possible thing you can do with the HttpRequest class is connect to a web server and read some data. Listing 27-1 shows how this is done. Listing 27-1 Example Use of the HttpRequest Class 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
package javafxdata; import import import import import import import
javafx.io.http.HttpRequest; javafx.scene.image.Image; javafx.scene.image.ImageView; javafx.scene.Scene; javafx.stage.Stage; javax.imageio.ImageIO; javafx.ext.swing.SwingUtils;
var image:Image; var r:HttpRequest = HttpRequest { location: "https://duke.dev.java.net/images/rover/OnBridge11small.jpg" sink: new ByteArrayOutputStream() onDoneRead: function() { var os = r.sink as ByteArrayOutputStream; var is = new ByteArrayInputStream(os.toByteArray());
19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41
var bimg = ImageIO.read(is); is.close(); os.close(); image = SwingUtils.toFXImage(bimg); } }; r.start(); Stage { var scene:Scene; title : "HttpRequest Example #1" scene: scene = Scene { width: 350 height: 280 content: [ ImageView { image: bind image x: bind (scene.width - image.width)/2 y: bind (scene.height - image.height)/2 } ] } }
The code on lines 12 to 25 creates an instance of the HttpRequest class and then calls its start() function, which assigns an integer identifier to the request, installs it in the id variable of the HttpRequest1 object and initiates the operation in a background thread. This simple example reads a stream of data that represents an image by installing its URL in the location variable of the HttpRequest and making an HTTP GET request, which is the default. The details of connecting to the web server, making the request, reading the response, and buffering the returned data are all encapsulated in the HttpRequest class and are carried out asynchronously. Under normal circumstances, a request will run to completion, but you can interrupt it by invoking the stop() function.2 As you'll see, the HttpRequest class provides a large number of variables and callbacks that you can use to monitor the progress of an operation, but here we just want to process the data once it is available. To do so, we create a ByteArrayOutputStream and install it in the sink variable. As data is received, it will be written to this stream, 1
An HttpRequest object can only be used to make one request. If you invoke the start() func-
tion for a second time, an IllegalStateException will be thrown. 2
The start() and stop() functions are inherited by HttpRequest from the
javafx.async.Task class, which we'll discuss later in this chapter.
and the onDoneRead function will be called (in the main thread of the JavaFX application) when the whole response has been read. In Listing 27-1, the onDoneRead function creates a ByteArrayInputStream from the data in the ByteArrayOutputStream. In this case, the data is an image, so we use the Java ImageIO class to convert the content of the ByteArrayInputStream into a BufferedImage, which is then converted to a JavaFX Image object by the SwingU3 tils.toFXImage() function on line 22. Finally, the image is displayed in an ImageView, giving the result shown in Figure 27-1.
Figure 27-1 An image retrieved from a web server using the
HttpRequest class
Lifecycle of an HTTP Request To initiate a connection to an HTTP server, you need to set the location variable to the URL of the resource that you want to operate on and the method variable to the HTTP operation to be performed. The HttpRequest class has constant definitions for each of the valid HTTP operations. In Listing 27-1, the method variable was defaulted to HttpRequest.GET. Later in this chapter, you'll see examples of POST, PUT, and DELETE operations. If you need to write data as part of the request (which would be the case for a POST or PUT operation), you would wrap the data in an InputStream and install it in the source variable. Similarly, if you expect to receive data, you create an OutputStream into which the data can be written and assign it to the sink variable, as we did in Listing 271. You'll see examples of both reading and writing data in the following sections. The overall lifecycle of an HTTP GET request is as follows: 1. The location and method variables of the HttpRequest object are set. 3
BufferedImage is part of the desktop profile, so this code will not run on a mobile device.
2. The operation is started by calling the start() function of the HttpRequest object. 3. The operation is initiated in a background thread. 4. A connection to the server is attempted. 5. The connection either succeeds and the operation continues, or an error is reported. 6. The request is sent to the web server, along with any HTTP headers in the headers variable of the HttpRequest object. 7. The response is read, starting with the HTTP headers. 8. The response code and response message are extracted from the HTTP headers. 9. If the server rejected the request, an error is notified. 10. The headers are fully processed. 11. The body of the response is read and made available to the application. 12. Completion of the request is notified. The HttpRequest class has a number of variables and a corresponding set of function variables that you can use to be notified of state changes as an operation proceeds through its lifecycle. The variables that are relevant to an HTTP GET operation are listed in Table 27-1. Note that there are additional variables and functions that are used in the course of a POST or PUT operation, which we cover later in this section. In almost all cases, each status variable is paired with a callback function that is called after the variable's value has been set. This means that you can usually handle a change in state either by attaching a trigger or a binding to a state variable or by assigning a function to one of the callback function variables.4 Table 27-1 Variables and Callback Functions That Track the Status of an HTTP GET Request Variable
Variable Type Function
Function
started
Boolean
exception
Exception onException Exception
Set/Function Called
Argument Type
connecting Boolean
4
onStarted
None
onConnecting None
When the operation is initiated. Whenever an exception occurs. When the connection to the server is initiated.
You can also monitor progress by using variables that the HttpRequest class inherits from
javafx.async.Task, which we'll discuss in "Tasks and Progress Monitoring" section, later in this chapter.
doneConnect Boolean
onDoneConnect
None
When the connection to the server has been made.
readingHeaders
Boolean
onReadingHeaders
None
When the HTTP headers sent by the server are being read. The headers that are defined by the HTTP 1.1 protocol specification are all represented by constants declared by the HttpHeaders class.
responseCode
Integer
onResponseCode
Integer
When the server's response code has been received. The possible values are defined as constants in the HttpStatus class.
responseMessage
String
onResponse- String Message
When the server's response message has been received. The response message is a textual representation of the response code.
error
InputStream
onError
InputStream
The value of an error response from the server, if there is one.
N/A
N/A
onResponseHeaders
String[]
When the response headers are available to be read.
doneHeaders Boolean
onDoneHeaders
None
When all the HTTP headers have been processed.
reading
Boolean
onReading
None
When the body of the response is being read.
toRead
Long
onToRead
Long
When the total number of bytes of response data is known.
read
Boolean
onRead
Long
As bytes of data are received from the server.
input
InputStream
onInput
InputStream
When the response body is available to be read. This variable and its associated callback function are used only when the sink variable is not set. See the section "A GET Request to a Valid HTTP URL" for details.
doneRead
Boolean
onDoneRead
None
When all the body has been read.
done
Boolean
onDone
None
When the operation is complete, successfully or otherwise.
The exception variable is set (and the onException function called) if an exception occurs at any point and the rest of the operation is aborted. For example, if the value of the location variable is not a valid URL or if the connection attempt fails, an exception will be reported. By contrast, if the connection succeeds but the server cannot return the requested data, the response code and response message are set to a value that gives the reason for the error and, in some cases, some additional information may be available through the error variable. On completion of the operation, whether or not it is successful, the done variable is set to true and the onDone function is called. This is always the final step in the lifecycle. It is important to note that, like all JavaFX objects, an HttpRequest must be created and used exclusively in the main thread of the JavaFX application. The process of connecting to the web server and reading/writing data are actually performed in a background thread, but the variables and callback functions in the HttpRequest object are always set and called in the main thread of the application. This means that application code and triggers are always called in the main thread, which is necessary because JavaFX does not support access to data from more than one thread. If you refer back to Listing 27-1, the process of reading the image data is initiated by the invocation of the start() function on line 25,but it will not be complete when this function returnsâ&#x20AC;&#x201D;receipt of data will be notified to the onDoneRead function at some
future point and completion of the operation to the onDone function, which is not used in this example.
GET Requests The code in the file javafxdata/HttpRequestExample2.fx creates a user interface with an input field into which you can type a URL, a button that can be pressed to perform a GET request to that URL, and a textbox in which the results of the operation are displayed. The doRequest() function builds the HttpRequest, attaches triggers to the variables, and installs callbacks in all the function variables listed in Table 27-1. The triggers and callback functions all write tracking information to the textbox so that the order in which these events occur can be seen. Here is an extract from this function that shows a typical callback function and trigger: function doRequest(url:String) { r = HttpRequest { location: url sink: new ByteArrayOutputStream() // Callback functions onConnecting: function() { addText("onConnecting\n"); } // // Code omitted // // State variables var connecting = bind r.connecting on replace { addText("connecting -> {connecting}\n") // // Code omitted // } r.start(); }
When a callback function is invoked, its name and relevant information derived from its argument, if it has one, are recorded in the textbox. When the value of a state variable changes, its name and value are shown, separated by an arrow. In the sections that follow, we use this code to examine the results of several different GET requests. Figure 27-2 shows some typical output.
Figure 27-2 Output from an HttpRequest
A GET Request to an Invalid URL For our first example, we enter the URL http://no.such.url into the text field and click the Go button. This causes the following to be written to the textbox:5 started -> true onStarted connecting -> true onConnecting exception -> java.net.UnknownHostException: no.such.url onException: java.net.UnknownHostException: no.such.url done -> true onDone
As you can see, the request begins by notifying that it has started and is attempting to make a connection to the server. Because the URL is invalid, the attempt fails, and this results in an UnknownHostException being written to the exception variable of the HttpRequest, and the onException callback function is then called. As this terminates
5
Here and in the examples that follow, we do not show output that results from initialization of the HttpRequest object.
processing of the request, the done variable is set to true, and the onDone callback is called.
A GET Request to a URL with an Unknown Protocol Using an unknown protocol results in an event shorter interaction than the one you have just seen. Here's the result of attempting to connect to the URL xyzzy://no.such.url: started -> true onStarted exception -> java.net.MalformedURLException: unknown protocol: xyzzy onException: java.net.MalformedURLException: unknown protocol: xyzzy done -> true onDone
Because the protocol part of the URL is not recognized, no connection handler class is found, so this request doesn't even get as far as attempting a connection. Instead, an exception is reported.
A GET Request to a Valid HTTP URL For our next example, we type the URL http://java.sun.com into the input field and click the Go button. Because this is a valid URL, the resulting output shows the complete lifecycle for an HTTP GET request: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 22 23 24 25 26 27 28 29 30
started -> true onStarted connecting -> true onConnecting doneConnect -> true onDoneConnect readingHeaders -> true onReadingHeaders responseCode -> 200 onResponseCode: 200 responseMessage -> OK onResponseMessage: OK onResponseHeaders : HTTP/1.1 200 OK server: Sun-Java-System-Web-Server/7.0 date: Sun, 26 Apr 2009 14:03:31 GMT content-type: text/html;charset=ISO-8859-1 set-cookie: JSESSIONID=B6E70BFD3EA616ADA7C7BDB4E2F129E5; Path=/ connection: close doneHeaders -> true onDoneHeaders reading -> true onReading toread -> -1 onToRead, length = -1 read -> 4156 onRead: 4156 bytes read -> 5616 onRead: 5616 bytes read -> 7076
31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56
onRead: 7076 bytes read -> 10828 onRead: 10828 bytes read -> 14580 onRead: 14580 bytes read -> 18332 onRead: 18332 bytes read -> 22084 onRead: 22084 bytes read -> 25836 onRead: 25836 bytes read -> 29588 onRead: 29588 bytes read -> 33340 onRead: 33340 bytes read -> 37092 onRead: 37092 bytes read -> 40844 onRead: 40844 bytes read -> 45236 onRead: 45236 bytes doneRead -> true onDoneRead Read a total of 45236 bytes done -> true onDone
The first six lines show the process of successfully connecting to the web server at java.sun.com. At this point, the HTTP GET request is transmitted and then the HttpRequest implementation starts reading the server's response. On lines 7 to 20, the server's response and the HTTP headers are received and processed. As you can see, the start and end of this part of the exchange are clearly delineated by the onReadingHeaders call on line 8 and the onDoneHeaders call on line 20. Strictly speaking, the response code is not part of the HTTP header block, but in the HttpRequest lifecycle it is reported at the start of header processing. The server's response code is stored in the responseCode variable, and a text string that describes the response is stored in the responseMessage variable. The possible response codes and their meanings are listed in the HTTP 1.1 protocol specification, which you can find at http://www.w3.org/Protocols/rfc2616/rfc2616.html. The HttpStatus class defines constants that correspond to these response codes. In this case, the request was successful to the response code is 200 (which is also the value of the constant HttpStatus.OK), and the response message is simply "OK." In some cases, you might want to check the value of the response code and take specific action, but here this is not necessary. The headers themselves each have a name and an associated string value. These are collected into a map, which is held by the HttpRequest object, and the header names are held in a sequence that is passed as an argument to the onResponseHeaders callback. Here's the code in this function that prints the header names and values: onResponseHeaders: function(headers) {
addText("onResponseHeaders"); showHeaders(headers, r) }
The showHeaders() function (which is part of the example code) looks like this: function showHeaders(headers:String[], r:HttpRequest) { for (header in headers) { addText(" {header}: "); addText("{r.getResponseHeaderValue(header)}\n"); } }
The name of each header in the response is obtained from the sequence that is passed to the onResponseHeaders callback. The corresponding header value is obtained by calling the getResponseHeaderValue() function of the HttpRequest class, which requires a header name as its argument and returns the value of that header, or an empty string if there was no header with the given name in the server's response. This function is one of two that can be used to get response header-related information from an HttpRequest object: public function getResponseHeaderNames():String[]; public function getResponseHeaderValue(name:String):String;
These functions can be called at any time, but return meaningful values only after the headers have been decoded.
Note The response headers are not stored in the headers variable of the HttpRequest object. This variable is used only to set the outgoing request headers, as you'll see later in this chapter.
The response data, if any, follows the headers. In this case, the response is the HTML for the home page at java.sun.com. The sequence starts with notification that reading has begun and ends with a callback that marks the end of the processing of the data. On lines 24 and 25, the total number of bytes that will be read is stored in the toread variable and also passed to the onToRead callback function. In this case, the number of bytes is 1, which indicates that the size of the data is not known in advance. The data length is actually taken from the content-length header of the server's response, if it was present. In this example, this header was not present because the server is using "chunked" encoding, which is typically used to stream data from the server over a long period of time, or to return a document or other data that is constructed dynamically and for which the size will not be known until it has been generated. As the data is being read, the total length so far received is stored in the read variable and reported to the onRead callback. It is also written to the OutputStream (in this case a ByteArrayOutputStream) assigned to the sink variable. In this example, the data is 45,236 bytes long and is delivered in 13 chunks. The updates to the read variable and
the onRead callback are provided to allow you to display progress in the user interface. Typically, you defer handling the input until it has all been read, which is signaled by the onDoneRead callback function. Here is the code that is assigned to this callback function for this example: onDoneRead: function() { addText("onDoneRead\n"); var os = r.sink as ByteArrayOutputStream; var is = new ByteArrayInputStream(os.toByteArray()); countStream(is); os.close(); }
The data is made accessible by creating a ByteArrayInputStream wrapping the content of the ByteArrayOutputStream that was assigned to the sink variable, which is then passed to the countStream() function, which simply counts the actual bytes that were received: function countStream(is:InputStream) { var count = 0; try { while (is.read() != -1) { count++ } } finally { is.close(); } addText("Read a total of {count} bytes\n"); }
At this point, the request is completed, so the done variable is set to true, and the onDone function is called to signal the end of the request lifecycle. An alternative mechanism is available for reading the response data that does not require you to create an OutputStream for the sink variable. If the sink variable is null, the runtime creates its own buffer in which the response data is held until it has all been received. At this point, it is wrapped in an InputStream that is then stored in the input variable and also passed to the onInput callback. This mechanism is used by the code in the file javafxdata/HttpRequestExample3.fx. Here's a snapshot of the output from this example, reading the same data from the java.sun.com website: 22 23 24 25 26 27 28 29 30 31 32
reading -> true onReading toread -> -1 onToRead, length = -1 read -> 26 onRead: 26 bytes read -> 4141 onRead: 4141 bytes read -> 5601 onRead: 5601 bytes read -> 7061
33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83
onRead: 7061 bytes read -> 8192 onRead: 8192 bytes read -> 8216 onRead: 8216 bytes read -> 10805 onRead: 10805 bytes read -> 14557 onRead: 14557 bytes read -> 16384 onRead: 16384 bytes read -> 16408 onRead: 16408 bytes read -> 18301 onRead: 18301 bytes read -> 22053 onRead: 22053 bytes read -> 24576 onRead: 24576 bytes read -> 24600 onRead: 24600 bytes read -> 25797 onRead: 25797 bytes read -> 29549 onRead: 29549 bytes read -> 32768 onRead: 32768 bytes read -> 32792 onRead: 32792 bytes read -> 33293 onRead: 33293 bytes read -> 37045 onRead: 37045 bytes read -> 40797 onRead: 40797 bytes read -> 40960 onRead: 40960 bytes read -> 40984 onRead: 40984 bytes read -> 45145 onRead: 45145 bytes read -> 45171 onRead: 45171 bytes read -> 45226 onRead: 45226 bytes read -> 45236 onRead: 45236 bytes input -> com.sun.javafx.io.http.impl.WaitingInputStream@7bb290 onInput: 45236 bytes doneRead -> true onDoneRead
The important difference between this and our earlier example can be seen on lines 80 and 81. In this case, because the sink variable in the HttpRequest object was null, the runtime creates an InputStream to contain the response data, stores it in the input
variable, and passes it to the onInput callback. The data must either be processed immediately or copied elsewhere to be used later. It is not possible to access the data later from the onDoneRead callback, as was done in our previous example because it will have been discarded by that time that callback is invoked. In this example, the InputStream is simply passed to the countStream() function that you saw earlier: onInput: function(is) { addText("onInput: "); countStream(is)}
Warning When using this mechanism, if the response from the web server includes any data, it will always be buffered, and an InputStream will be created and assigned to the input variable. No further progress with the request will be reported until this InputStream is closed. This is the case even if you do not register an onInput callback function. Therefore, if you do not initialize the sink variable and you make a request that is expected to return data, you must provide an onInput callback that will at least close the InputStream, In our example, the InputStream is closed in the countStream() function.
A GET Request to a URL That Does Not Exist If you attempt to connect to a resource that does not exist on a web server that does exist, you will get back a response code (HttpStatus.NOT_FOUND, which has the value 404) reporting that the requested resource is not found. In addition, an InputStream that contains some supplementary information provided by the server may be stored in the error variable of the HttpRequest. Here's what happens if you try to access the URL http://java.sun.com/nosuchpage: 1 2 3 4 5 6 7 8 9 10 11 12 13
14 15 16 17 18 19 20 21 22
started -> true onStarted connecting -> true onConnecting doneConnect -> true onDoneConnect readingHeaders -> true onReadingHeaders responseCode -> 404 onResponseCode: 404 responseMessage -> Not found onResponseMessage: Not found error -> sun.net.www.protocol.http.HttpURLConnection$HttpInputStream@1e5579 4 onError: Error: <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"> <html> <head> <title>Sun Microsystems</title> (Further content not shown)
23 24 25 26 27 28 29 30 31 32 33 34
</html> onResponseHeaders : server: Sun-Java-System-Web-Server/7.0 date: Wed, 15 Apr 2009 19:51:00 GMT transfer-encoding: chunked doneHeaders -> true onDoneHeaders exception -> java.io.FileNotFoundException: http://java.sun.com/nosuchpage onException: java.io.FileNotFoundException: http://java.sun.com/nosuchpage done -> true onDone
As in our previous example, the lifecycle begins with a successful connection to the web server (lines 1 to 6) and then the headers are returned. The response code, received on lines 9 and 10, and the response message, seen on lines 11 and 12, indicate that the resource does not exist. In addition to this, the error variable is set to point to an InputStream subclasses that contains additional data supplied by the web server (see line 13), and this InputStream is also passed as an argument to the onError callback on line 14. In our example code, the function assigned to this callback reads the content of that stream and writes it to the text component in the user interface (see lines 15 to 24). In this case, the content is some HTML that is intended to be displayed to the user, which is not reproduced here because it is too large. The error is also automatically converted to a FileNotFoundException that is stored in the exception variable and delivered to the onException callback on lines 31 and 32.
Note Unlike the InputStream assigned to the input variable, you are not required to read the content of the InputStream in the error variable. If you don't read it, it will be discarded and the stream will be closed.
Retrieving Data from an FTP Server Despite its name, the HttpRequest class can be used to read data from any source that has a URL with a protocol that is supported by the underlying Java platform. This includes local files (using the file: protocol) and FTP servers (using the ftp: protocol). Here's the result of reading the document /etc/motd from the FTP server at ftp.gimp.org, using the URL ftp://ftp.gimp.org/etc/motd: 1 2 3 4 5 6 7 8
started -> true onStarted connecting -> true onConnecting doneConnect -> true onDoneConnect readingHeaders -> true onReadingHeaders
9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
onResponseHeaders content-length: 433 doneHeaders -> true onDoneHeaders reading -> true onReading toread -> 433 onToRead, length = 433 read -> 433 onRead: 433 bytes input -> com.sun.javafx.io.http.impl.WaitingInputStream@1162212 onInput: 433 bytes doneRead -> true onDoneRead done -> true onDone
If you compare this with the successful requests to an HTTP server shown in the "A GET Request to a Valid HTTP URL" section, earlier in this chapter, you see that the life-
cycle is broadly the sameâ&#x20AC;&#x201D;the connection is made on lines 1 to 6, the receipt of data is reported on lines 12 to 17, the data is made available to be read via an InputStream on lines 18 and 19, and the lifecycle concludes with the done variable being set to true on lines 22 and 23. However, because this example uses the FTP protocol rather than HTTP, there is no response code, no response message, and only a single header that reports the size of the document being retrieved. Even this header is not realâ&#x20AC;&#x201D;it is constructed based on information returned by the FTP server. The data can be read (and in fact must be read, for the same reason as data returned from an HTTP server must be read) from the InputStream stored in the input variable and passed to the onInput callback, in the same way as any other HttpRequest.
PUT Requests An HTTP PUT request is typically used to upload new data to a web server. The data is supplied in the body of the request, following the headers. The HttpRequest class provides a small number of variables and callback functions, in addition to those listed in Table 27-1 that are used when uploading data. These variables and callbacks are listed in Table 27-2. Table 27-2 Variables and Callback Functions That Track the Status of an HTTP PUT or HTTP POST Request Variable
Variable Type
Function
Function
writing
Boolean
onWriting None
Set/Function Called
Argument Type Just before the request data is written.
output
OutputStream onOutput OutputStream
When the request data is required. Not set or called when the source variable is not null. See the text for details.
toWrite
Long
onToWrite Long
When the data to be written has been supplied and is about to be written.
written
Long
onWritten Long
As bytes of request data are written. May be set/called multiple times.
onDoneWrite
When all the request data has been written.
doneWrite Boolean
None
When uploading data, you need to set the HTTP headers that indicate the type of the data, its encoding, and its length. The headers variable of the HttpRequest object is provided for this purpose. The data itself can be supplied in either of two waysâ&#x20AC;&#x201D;you can wrap it in an InputStream and reference it from the source variable, or you can allow the HttpRequest class to create an OutputStream that you write to when the data is required. We'll show both possibilities in this section. The example code in the file javafxdata/HttpRequestExample4.fx performs a PUT request and supplies its data via the source variable. Like the rest of the examples in this section, it uses a servlet that you need to compile and deploy. In the Preface to this book, you will find instructions for installing the integrated development environment (IDE) plug-ins required to run the servlet. To start the servlet, right click the ExternalData project in the IDE and click Run. Here, we assume that it is running in a web container that is listening on port 8080 of the same computer as the one on which the examples are being run. When you run the example, you will see a familiar user interface that allows you to enter a URL, initiate the request, and then view the results, as shown in Figure 27-3. The URL used is http://localhost:8080/ExternalData/HttpRequestServlet/a/b/c/d. The first part of this URL (up to and including HttpRequestServlet) refers to the servlet itself. The balance of the URL is the name of a file into which the uploaded data is to be stored. In fact, the servlet does not actually store the dataâ&#x20AC;&#x201D;it just returns a response message that indicates the content type of the data and how many bytes were received, both of which come from the HTTP headers set in the headers variable of the HttpRequest.
Figure 27-3 Output from an HTTP PUT request
Here is the code that creates the HttpRequest for this example. As before, for the sake of brevity, we omit the lines that set triggers on the state variables and install callback functions: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
function doRequest(url:String) { var data = "The data to be written\n"; var bytes = data.getBytes("UTF-8"); r = HttpRequest { location: url method: HttpRequest.PUT source: new ByteArrayInputStream(bytes); headers: [ HttpHeader { name: HttpHeader.CONTENT_TYPE value: "text/plain" }, HttpHeader { name: HttpHeader.CONTENT_LENGTH value: "{bytes.length}" }, HttpHeader { name: HttpHeader.CONTENT_ENCODING value: "UTF-8"
20 21 22 23 24 25 26
}, ] // Code omitted } r.start(); }
Lines 2 and 3 convert the data to be sent from a String to an array of bytes in the UTF-8 encoding. The location variable of the HttpRequest is set to the URL of the servlet plus the name of the imaginary file to be written, while the method variable is set to indicate a PUT operation. The source variable is set to a ByteArrayInputStream wrapping the data bytes to be uploaded and three HTTP headers, each an instance of the HttpHeader class, are installed in the headers variable. Each header has a name, as defined by the HttpHeader class, and a corresponding value. The output from this request is shown here: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35
started -> true onStarted connecting -> true onConnecting doneConnect -> true onDoneConnect writing -> true onWriting towrite -> -1 onToWrite, length = -1 written -> 23 onWritten, length = 23 doneWrite -> true onDoneWrite readingHeaders -> true onReadingHeaders responseCode -> 200 onResponseCode: 200 responseMessage -> Got 23 bytes, type text/plain onResponseMessage: Got 23 bytes, type text/plain onResponseHeaders : HTTP/1.1 200 Got 23 bytes, type text/plain x-powered-by: Servlet/2.5 server: Sun Java System Application Server 9.1_02 content-type: text/html; charset=iso-8859-1 content-length: 0 date: Sun, 26 Apr 2009 15:45:33 GMT doneHeaders -> true onDoneHeaders reading -> true onReading onToRead, length = 0 doneRead -> true onDoneRead done -> true onDone
Lines 7 to 14 show the process of writing the data to be uploaded, starting with the writing variable being set to true on line 7. As you can see, the onToWrite variable has the value -1. This is always the case when you supply the data to be uploaded through the source variable. Lines 11 to 14 indicate that 23 bytes of data were actually
written, which is the correct length for the data that is supplied in this case. An alternative means of supplying the upload data is used in the example that you'll find in the file javafxdata/HttpRequestExample5.fx. Here, the data is not prepared in advance. Instead, the source variable has it default value (which is null). This causes the HttpRequest class to call the onOutput callback function when it is ready to send the data to the server. This function is passed an OutputStream to which the data must be written. Here is the code from this example that creates and initiates the HttpRequest: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32
function doRequest(url:String) { r = HttpRequest { location: url method: HttpRequest.PUT headers: [ HttpHeader { name: HttpHeader.CONTENT_TYPE value: "text/plain" }, HttpHeader { name: HttpHeader.CONTENT_LENGTH value: "{data.length()}" }, HttpHeader { name: HttpHeader.CONTENT_ENCODING value: "UTF-8" }, ] // Code omitted onOutput: function(os) { textArea.append("onOutput called\n"); var bytes = data.getBytes("UTF-8"); os.write(bytes); os.close(); } // Code omitted } r.start(); }
As you can see, the source variable is not set. Instead, the onOutput callback function is implemented. When it is called, the data to be written is converted to bytes in the UTF-8 encoding and written to the OutputStream that is passed to it, following which the stream is closed.
Warning If the stream is not closed, no further progress will be made with the request. The close() call on line 26 is, therefore, essential.
Here is some typical output from this example: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37
started -> true onStarted connecting -> true onConnecting doneConnect -> true onDoneConnect writing -> true onWriting output -> onOutput called towrite -> 23 onToWrite, length = 23 written -> 23 onWritten, length = 23 doneWrite -> true onDoneWrite readingHeaders -> true onReadingHeaders responseCode -> 200 onResponseCode: 200 responseMessage -> Got 23 bytes, type text/plain onResponseMessage: Got 23 bytes, type text/plain onResponseHeaders : HTTP/1.1 200 Got 23 bytes, type text/plain x-powered-by: Servlet/2.5 server: Sun Java System Application Server 9.1_02 content-type: text/html; charset=iso-8859-1 content-length: 0 date: Sun, 26 Apr 2009 16:43:02 GMT doneHeaders -> true onDoneHeaders reading -> true onReading onToRead, length = 0 doneRead -> true onDoneRead done -> true onDone
As you can see, this is identical to the previous case apart from lines 9 and 10, which report the setting of the output variable and the invocation of the onOutput function. This is the point at which the preceding code is executed to write the request data to the OutputStream supplied by the HttpRequest class.
POST Requests In terms of its mechanics, a POST request is pretty much the same as a PUTâ&#x20AC;&#x201D;you set the method, location, headers that indicate the content type, length and encoding, supply the data to be sent in the source variable, start the request, and eventually you handle the response. POST requests are often used to submit a query (typically derived from an input form) to a web server. Because this is such a common operation, we'll take a look at how to submit the content of a form using the HttpRequest class. Browsers model a form as a set of parameter/value pairs, where each parameter is typically represented by an input field on the form and the value is taken from the content of that input field. When a form is submitted, the set of parameter/value pairs is encoded and sent to the web server in the body of a POST request.6 Let's suppose that the form has fields called param1 and param2 and that these fields contain the values value #1 and value #2, respectively. The data that is sent in the request body for this form would look like this: param1=value+%231&param2=value+%232
As you can see, the name/value pairs are separated by an ampersand. If there were three fields on the form, there would be three such pairings and therefore two ampersands. The values themselves have been encoded according to rules defined by the HTML specification. In essence, these rules require that all spaces in a form value be replaced by a + character and all nonalphanumeric values be replaced by their numeric equivalent, preceded by a % character. Here, the # characters have been encoded as %23, because 0x23 is the ASCII representation of #. This set of encoding rules is referred to as URL encoding because it allows you to include form data in the URL of a GET request as well as in the body of a POST request. Performing this conversion is tedious, but fortunately there is a class called URLConverter in the javafx.io.http package that will handle the details for you. This class has several functions that you can use to work with data that is or needs to be URL encoded. The particular function that we are going to use for this example is declared as follows: public function encodeParameters(parameters:Pair[]):String
Each name/value pair is represented as an instance of the javafx.data.Pair class, which holds both the name and the associated value. The result of the function call is the previous encoded string. The code that creates and submits the POST request can be found in the file javafxdata/HttpRequestExample6.fxâ&#x20AC;&#x201D;here's an extract that shows the important details. As before, when creating the HttpRequest object, we install triggers and callback functions that report the progress of the request as it is executed, but we do not show those details here: 6
Forms can also be submitted using a GET operation. We cover this at the end of this section.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31
function doRequest(url:String) { var params = [ Pair { name: "param1" value: "value #1" }, Pair { name: "param2" value: "value #2" }, ]; var converter = URLConverter {}; var postData = converter.encodeParameters(params); addText("Encoded data: [{postData}]"); var bytes = postData.getBytes("UTF-8"); r = HttpRequest { location: url method: HttpRequest.POST source: new ByteArrayInputStream(bytes); headers: [ HttpHeader { name: HttpHeader.CONTENT_TYPE value: "application/x-www-form-urlencoded" }, HttpHeader { name: HttpHeader.CONTENT_LENGTH value: "{bytes.length}" }, HttpHeader { name: HttpHeader.CONTENT_ENCODING value: "UTF-8" }, ] // Code omitted } r.start(); }
The code on lines 2 to 5 creates the name/value pairs for the form by using Pair objects. On lines 6 and 7, we create a URLConverter instance and invoke its encodeParameters() function to apply the URL encoding to the form parameters to produce the representation required for the request body. On line 9, we convert the parameter string to bytes in the UTF-8 encoding. The HttpRequest is almost identical to the one in the PUT example shown earlier in this chapter. The only differences are the method, which is HttpRequest.POST in this case, and the content type header, which is application/x-www-form-urlencoded. This special value indicates that the URL encoding rules described above have been used to encode the data in the body of the request. That data is passed to the HttpRequest in form of a ByteArrayInputStream assigned to the source variable. If you run this code and use the same URL as the one in the PUT example in the previous section, you will see the result of the operation in the usual place in the user interface. When it receives a POST request, the servlet reads the parameter/value pairs and values from the body and returns them as part of the response code, separated by vertical bars, to demonstrate that they have been correctly encoded. Here's an extract from the output that shows the servlet's response: responseCode -> 200
onResponseCode: 200 responseMessage -> param1=value #1|param2=value #2| onResponseMessage: param1=value #1|param2=value #2|
Form data can also be submitted by using a GET request. In this case, the request body would be empty and the URL encoded form data is appended to the rest of the URL and separated from it by a ? character, like this: http://host/servletpath?param1=value+%231&param2=value+%232
DELETE Requests As it name suggests, the HTTP DELETE operation is used to request the removal of the resource identified by the target URL. In terms of the HttpRequest class, the resource to be removed is specified by the location variable, and the method variable is set to HttpRequest.DELETE. The following code extract, from the file javafxdata/HttpRequestExample7.fx, shows how to create and perform a DELETE request: function doRequest(url:String) { r = HttpRequest { location: url method: HttpRequest.DELETE // Code to add triggers and callback functions omitted } r.start(); }
Running this example and entering the same URL as that used in the PUT and POST examples earlier causes the servlet to respond with a message that indicates that the DELETE request was understood (although the example servlet does not actually delete anything): onStarted onConnecting onDoneConnect onReadingHeaders onResponseCode: 200 onResponseMessage: Deleted file /a/b/c/d onResponseHeaders : HTTP/1.1 200 Deleted file /a/b/c/d x-powered-by: Servlet/2.5 server: Sun Java System Application Server 9.1_02 content-type: text/html; charset=iso-8859-1 content-length: 0 date: Mon, 27 Apr 2009 20:40:15 GMT onDoneHeaders onDone
Using RESTful Web Services
Two different styles of web service are in use today: SOAP-based web services and RESTful web services. To access a SOAP-based web service, you create an XML payload, wrap it in HTTP, and POST it to a web server on which the service is running. The XML payload is constructed from a set of rules contained in a WSDL (Web Service Definition Language) file, which specifies the operations that the service provides, the parameters that you need to supply to those operations, and the value or values that will be returned. The process of constructing the payload is sufficiently nontrivial that nobody ever writes code to do itâ&#x20AC;&#x201D;instead, there are tools that convert a WSDL file to a Java class that has a method for each of the available operations. To invoke one of the web service's operations, you just call the corresponding method in the generated Java class, which takes care of converting the request and its parameters to XML, opening an HTTP connection to the web server, retrieving the response, and converting it back to Java objects. This whole process is synchronous to the method caller, so the calling thread will be blocked until the results of the call have been received. Because of the synchronous nature of the generated class, to access a SOAP-based web service from JavaFX, you need to invoke its methods in a background thread and then display the results in the main thread of your application. We'll look at the support that JavaFX provides for asynchronous operations of this type in the context of database access in the "Asynchronous Operations and Database Access" section, later in this chapter. If using a SOAP-based web services sounds like a lot of work, you will probably be pleased to hear that it is much easier to use a RESTful web service. RESTful web services are usually not formally defined in an XML file, and there is no need to use a code generator to create the access methods. To use a RESTful web service, you simply identify the object to be operated on by its URL, set the HTTP method to indicate the operation to be performed, and use the HttpRequest class to initiate the operation. As you have seen, HttpRequest does its job in a background thread and notifies completion in the application's main thread, which means that your application's user interface will not freeze while the operation is in progress. The result of a RESTful web service operation is usually XML, although some services encode their response in JSON (JavaScript Object Notationâ&#x20AC;&#x201D;see http://www.json.org). To interpret the reply, you need either an XML or a JSON parser. JavaFX has both, wrapped up in a single class called javafx.data.pull.PullParser. In this section, we take a look at how to access a RESTful web service from JavaFX. We start by looking at how you can use the PullParser class to parse XML, and we'll implement a simple JavaFX client that retrieves and displays information from the Twitter social networking website. Later, you'll see how to use the PullParser class to interpret the results of a web service that returns its results in the JSON encoding.
Parsing XML There are three different styles of XML parsing:
• DOM-style parsing: This converts an XML document into an in-memory tree in which each XML element is represented by a tree node. The nesting of XML elements is mirrored exactly by the nesting of nodes in the DOM (Document Object Model) tree. This style of parsing is very convenient if you want to convert XML into Java objects, but modeling a large XML document requires a correspondingly large commitment of memory. For this reason, DOM parsing is not usually supported on small-footprint devices like cell phones. • Stream parsing (or SAX parsing): In this style, the parser reads through the document, possibly as it is being received from its source, and notifies events to an application-supplied event handler. Examples of events are at the start of the document, start of an element, text, comments, processing instructions, end of an element, and end of the document. Stream parsing can be very efficient because it does not require the whole document to be in memory at the same time. • Pull parsing: Whereas a stream parser invokes application code as it reads through the XML document, a pull parser does nothing unless it is told to. An application using a pull parser processes a document by "pulling" elements that it is interested in when it needs them. The PullParser class in the javafx.data.pull package supports stream and pull parsing for both XML and JSON documents. Which of these modes you choose to use is largely a matter of taste, although as you'll see, pull parsing requires greater knowledge of the structure of an XML document than stream parsing. The variables of the PullParser class, which we will describe in detail in this section, are listed in Table 27-3. Table 27-3 Variables of the PullParser Class Variable
Type
Access Default Description
documentType
String
RW
XML
input
InputStream
RW
null
encoding
String
RW
"UTF-8" The encoding used for the input document
RW
false If true, causes text elements
The type of document to be parsed—either PullParser.XML or Pull-
Parser.JSON
ignoreWhiteSpace Boolean
The source of the XML or JSON to be parsed
that are entirely whitespace to be ignored
onEvent
funcRW tion(:Event) :Void
event
Event
R
null
Callback function to which parsing events are delivered The current parsing event
characterEncoding
String
R
The encoding of the input document as detected by the parser
line
Integer
R
The line number of the element currently being parsed
column
Integer
R
The column number of the element currently being parsed
Stream Parsing To use the PullParser class in streaming mode, you initialize it with an input source, set the documentType variable to either PullParser.XML or PullParser.JSON, install a function that will handle callbacks as the input is parsed, and then call the parse() function. Parsing is synchronous and will be completed before this call returns. The code in Listing 27-2, which you'll find in the file javafxdata/XMLParsing1.fx, parses XML held in a string and reports the events delivered to its callback function on the console. Listing 27-2 Stream Parsing with the PullParser Class 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
var xml = '<?xml version="1.0" encoding="UTF-8"?>' '<!--- Membership list -->' '<members>' ' <member>' ' <id>' ' <name>John Doe</name>' ' <address type="home">Here</address>' ' <address type="business">There</address>' ' </id>' ' <membership-type>Full</membership-type>' ' </member>' '</members>'; var parser = PullParser { documentType: PullParser.XML input: new ByteArrayInputStream(xml.getBytes("UTF-8")) onEvent: function(evt) { println(evt); } } parser.parse();
The PullParser object is created on lines 15 to 21 and initialized to parse XML (line 16) from the string assigned to the xml variable. A PullParser requires an InputStream as its source, so we first convert the string to an array of bytes encoded in UTF87 and then create a ByteArrayInputStream from the result. As the XML is parsed, events are reported to the function installed in the onEvent variable on lines 18 to 20. The argument passed to this function is of type javafx.data.pull.Event. The Event class is used when parsing both XML and JSON and has some variables that are set only when processing XML and others that are meaningful only when processing JSON. The variables that are relevant to XML processing are listed in Table 27-4. Table 27-4 Variables of the Event Class That Are Relevant to XML Parsing Variable
Type
type
Integer R
Access Description The event type, such as Pull-
typeName
String
R
The event type in human-readable form
qname
QName
R
The fully-qualified name of the current element
level
Integer R
text
String
Parser.START_ELEMENT
R
The nesting level of the current element, where the root element has level 0 The text associated with a TEXT element
Each event has a type, given by the type variable, which indicates which event is being reported; the typeName variable contains a human-readable representation of the event type which is useful for debugging purposes. The level variable specifies the nesting level of the XML element to which the event relates and the qname variable is its fully qualified name. XML element names are made up of two partsâ&#x20AC;&#x201D;a simple name and an optional prefix that indicates the namespace that defines the name. The qname variable is of type javafx.data.xml.QName, which is a class that encapsulates the element name, its namespace prefix, and the URI of the namespace itself. Simple XML documents, such as the one in this example (and those returned by some commonly used web services), do not use namespaces at all, which makes the document easier to read and your code easier to write because you only need to check the simple name of an element to identify it. The text variable is set to report a run of text in the document. We'll use the output from the code in Listing 27-2 to illustrate how the parser works. Later in this chapter, you'll see real-world examples that actually act on upon the results of an XML parsing operation: 1
type:7 typeName:START_DOCUMENT level:0 qname:null text:''
7
We chose UTF-8 because this is the encoding that the parser expects by default. You can use a different encoding if you want, provided that you set the encoding variable of the PullParser to reflect your chosen encoding.
2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56
namespaces:null attributes:null type:1 typeName:START_ELEMENT level:0 qname:members text:'' namespaces:{} attributes:{} type:4 typeName:TEXT level:0 qname:members text:' ' namespaces:null attributes:null type:1 typeName:START_ELEMENT level:1 qname:member text:'' namespaces:{} attributes:{} type:4 typeName:TEXT level:1 qname:member text:' ' namespaces:null attributes:null type:1 typeName:START_ELEMENT level:2 qname:id text:'' namespaces:{} attributes:{} type:4 typeName:TEXT level:2 qname:id text:' ' namespaces:null attributes:null type:1 typeName:START_ELEMENT level:3 qname:name text:'' namespaces:{} attributes:{} type:4 typeName:TEXT level:3 qname:name text:'John Doe' namespaces:null attributes:null type:2 typeName:END_ELEMENT level:3 qname:name text:'John Doe' namespaces:{} attributes:null type:4 typeName:TEXT level:0 qname:id text:' ' namespaces:null attributes:null type:1 typeName:START_ELEMENT level:3 qname:address text:'' namespaces:{} attributes:{type=home} type:4 typeName:TEXT level:3 qname:address text:'Here' namespaces:null attributes:null type:2 typeName:END_ELEMENT level:3 qname:address text:'Here' namespaces:{} attributes:null type:4 typeName:TEXT level:0 qname:id text:' ' namespaces:null attributes:null type:1 typeName:START_ELEMENT level:3 qname:address text:'' namespaces:{} attributes:{type=business} type:4 typeName:TEXT level:3 qname:address text:'There' namespaces:null attributes:null type:2 typeName:END_ELEMENT level:3 qname:address text:'There' namespaces:{} attributes:null type:4 typeName:TEXT level:0 qname:id text:' ' namespaces:null attributes:null type:2 typeName:END_ELEMENT level:2 qname:id text:' ' namespaces:{} attributes:null type:4 typeName:TEXT level:0 qname:member text:' ' namespaces:null attributes:null type:1 typeName:START_ELEMENT level:2 qname:membership-type text:'' namespaces:{} attributes:{} type:4 typeName:TEXT level:2 qname:membership-type text:'Full' namespaces:null attributes:null type:2 typeName:END_ELEMENT level:2 qname:membership-type text:'Full' namespaces:{} attributes:null type:4 typeName:TEXT level:0 qname:member text:' ' namespaces:null attributes:null type:2 typeName:END_ELEMENT level:1 qname:member text:' ' namespaces:{} attributes:null type:2 typeName:END_ELEMENT level:0 qname:members text:'' namespaces:{} attributes:null type:8 typeName:END_DOCUMENT level:0 qname:null text:'' name spaces:null attributes:null
The first and last events, which have the types PullParser.START_DOCUMENT and PullParser.END_DOCUMENT, respectively, report the beginning and end of the XML document. These events always occur outside of any XML element and so always have a level of zero, an empty element name and empty text. The events on lines 3 to 8 report the <members> and <member> elements that appear on lines 4 and 5 of Listing 27-2 and the whitespace text between them. When an opening element, such as <members>, is encountered, a START_ELEMENT event that reports the element name and its indentation level is generated. The <members> element is the root element of the document, so its level variable is set to 0, while the <member> element that is nested within it has indentation level 1. The whitespace that appears between these two elements is reported by a TEXT event. In most cases, you don't want to process text that is entirely whitespace, so events like this are not useful, and you can suppress them by setting the ignoreWhiteSpace variable to true. In the rest of this discussion, we ignore events that report whitespace text. The events on lines 15 to 20, repeated here for convenience, report the <name> element on line 7 of Listing 27-2: type:1 typeName:START_ELEMENT level:3 qname:name text:'' namespaces:{} attributes:{} type:4 typeName:TEXT level:3 qname:name text:'John Doe' namespaces:null attributes:null type:2 typeName:END_ELEMENT level:3 qname:name text:'John Doe' namespaces:{} attributes:null
The first event is generated when the <name> element is encountered, the second when the text content of the element is read, and the third when the closing </name> element is reached. Notice that the nested text is available from both the TEXT and END_ELEMENT events, which means that you can choose to process it on receipt of either event.8 On lines 8 and 9 of Listing 27-2, there are two <address> elements, each with a different value for the type attribute. The events that correspond to the first of these elements, which appear on lines 23 to 28 of the output, are shown here: type:1 typeName:START_ELEMENT level:3 qname:address text:'' namespaces:{} attributes:{type=home} type:4 typeName:TEXT level:3 qname:address text:'Here' namespaces:null attributes:null type:2 typeName:END_ELEMENT level:3 qname:address text:'Here' namespaces:{} attributes:null
As you can see, the name and value of an attribute is reported as part of a START_ELEMENT event. The attributes are not exposed as a variable of the Event class
but can be obtained from the following functions: public function getAttributeNames():Object[];
8
The text is not reported by the START_ELEMENT event because it won't have been read when this
event is generated.
public function getAttributeValue(name:Object):String;
The getAttributeNames() function returns a sequence containing the names of the attributes that appear in the element. Of course, if you know the name of the attribute whose value you require, which is the normal case, you don't need to call this function. Given the name of an attribute, you can get its value by calling the getAttributeValue() function, which returns an empty string if the named attribute does not appear in the element: // The following code returns "home" or "business" String value = evt.getAttributeValue("type");
The rest of the output follows the same pattern and should be easily understood. Note that the parser does not report the presence of XML processing instructions, like that shown on line 2 of Listing 27-2, or comments like the one on line 3.
Warning If you try to parse XML that is not valid, events will be reported until the parser detects an error, at which point an exception will be thrown. Because JavaFX script does not have a syntax that allows a function to declare that it could throw an exception, it is not obvious from the declaration of the parse() function that this might happen. If you are parsing XML that might not be well formed, you may need to catch the exception and recover gracefully.
Pull Parsing To use the pull parsing API, you create a PullParser in the same way as when using the streaming API, but you don't call the parse() function. Instead, you navigate the document structure by using the following functions: public public public public
function function function function
seek(element:Object):PullParser; seek(element:Object, level:Integer):PullParser; forward():PullParser; forward(count:Integer):PullParser;
The seek() functions work forward through the document from the current position until a given element is found. The single-argument variant looks for the element anywhere between the current position and the end of the document, while the second variant returns the element only if it occurs at the specified level of indentation. The element argument to both of these functions can be either a String or a QName. If it is a String, any element with the specified name will match, regardless of its namespace. To match on both a namespace and a name, use a QName instead. The forward() function skips forward by one event, while the forward(count) function skips forward by the specified number of events. The code in Listing 27-3, which is taken from the file javafxdata/XMLParsing2.fx, uses the pull parsing API to extract the name and home
address values from the same XML string as the one we used in the streaming example in Listing 27-2. Listing 27-3 Pull Parsing with the PullParser Class 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
var xml = <?xml version="1.0" encoding="UTF-8"?>' <!--- Membership list -->' <members>' <member>' <id>' <name>John Doe</name>' <address type="home">Here</address>' <address type="business">There</address>' </id>' <membership-type>Full</membership-type>' </member>' </members>'; var parser = PullParser { documentType: PullParser.XML input: new ByteArrayInputStream(xml.getBytes("UTF-8")) ignoreWhiteSpace: true } parser.seek("name", 3); parser.forward(); println("Name is '{parser.event.text}'"); parser.seek("address").forward(); println("Address is '{parser.event.text}'");
The PullParser object is created as before, except that in this case we don't install an event-handling function. Notice that we also set ignoreWhiteSpace to trueâ&#x20AC;&#x201D;this is usually a good idea when using the pull API, especially if you are going to skip events by using the forward(count) function, because it is easier to count the number of events to skip if you don't have to take into account whitespace in the document. The seek() call on line 21 skips forward until it finds a <name> element (in any namespace) at indentation level 3, which will locate the element on line 7 in Listing 273. When this element is reached, a START_ELEMENT event is written to the event variable of the PullParser and the function returns.9 The forward() call on line 22 skips 9
If you were to install an event handler while using the pull API, you would find that all the events
that are generated as the parser works through the document are delivered to it.
this event and causes the next one, which is the TEXT event for the <name> element, to be written to the event variable, from where is it is accessed by the println() function on line 23. This prints the following: Name is 'John Doe'
The code on line 25 locates the text for the first of the two <address> elements. It does this by searching for the element itself, which positions it at the START_ELEMENT event, moves forward one event to get the text. Notice that these operations are chained together, which makes the code more compactâ&#x20AC;&#x201D;this is possible because the seek() and forward() functions all have the PullParser object as their return value. Code written using the pull API can be much easier to understand than code that uses the streaming API, because it is linear, and you can easily see the order in which events will be processed. However, the pull API works well only if you can rely on the ordering of elements in the document that you are parsing. In this case, the code assumes that the <name> element appears before the <address> element, but suppose that the ordering of these elements were to be reversed: <id> <address type="home">Here</address> <address type="business">There</address> <name>John Doe</name> </id>
The seek() call on line 21 of Listing 27-3 will still find the <name> element, but the call on line 25 will not locate the <address> element because it has already been read and discarded. The second seek() call will actually read through the remainder of the document and will return when it reaches the end, leaving and END_DOCUMENT event in the parser's event variable. A Twitter Web Service Client. You have seen how to make HTTP requests and how to parse XML, so now it is time to put the two together and show you how to get some information from a web service and convert it into a form in which it can be displayed in a JavaFX application. The web service that we use provides a timeline that contains the twenty most recent public posts of a user of the Twitter social networking site. For this example, we will retrieve the timeline for the user InformIT.I chose to use this timeline for this example because, unlike some web services, it does not require you to register and get a key before you can use it and does not require authentication when you make a request. In addition, it can provide results in several different formats, including XML, which is the format that we will use here. To get the current content of this timeline, you just make an HTTP GET request to the URL http://api.twitter.com/1/statuses/user_timeline/informit.xml. In keeping with the spirit of RESTful web services, the GET method indicates that this is a read operation and the URL indicates the information that we want to read. The .xml suffix of the URL causes the results to be returned in XML format. You can find the official documentation for this service at http://apiwiki.twitter.com/Twitter-REST-API-Method%3A-statusesuser_timeline.
Having read the timeline, we will convert it into a sequence of objects that contains the parts of each message that we want to display. Here's the definition of the JavaFX class that represents each message, which you'll find in the file javafxdata/TwitterEntry.fx: public class TwitterEntry { public-init var user:String; public-init var imageURL:String; public-init var time:String; public-init var content:String; }
Each message will be displayed by a custom node called InformationNode, which we won't discuss in any detail in this chapter. If you are interested in how it works, you'll find the source code in the file javafxdata/InformationNode.fx. When this application is started, it shows only a title; once the web service call has completed, it parses the returned XML to create a sequence of TwitterEntry objects. The user interface converts each TwitterEntry object into an InformationNode and arranges them all in a VBox, which is itself wrapped in a ClipView and linked to a ScrollBar. Here's the code that actually displays the messages, from the file javafxdata/TwitterInformtITTimeline.fx, which you should run if you want to try out this application: clipView = ClipView { node: VBox { nodeHPos: HPos.CENTER content: bind for (entry in entries) { InformationNode { name: entry.user imageURL: entry.imageURL message: "{entry.time}\n{entry.content}" } } }
Invoking the Twitter Web Service The code that gets the content of the timeline can be found in the file javafxdata/TwitterClient.fx. The function that is used to call the web service is shown in Listing 27-4. Listing 27-4 Requesting the InformIT Timeline from the Twitter Web Service 1 2 3 4 5 6 7
public function getInformITTimeline( onDone:function(TwitterEntry[])) { var entries:TwitterEntry[] = null; var os = new ByteArrayOutputStream(); var req:HttpRequest = HttpRequest { location: "http://api.twitter.com/1/statuses/user_timeline/"
8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
"informit.xml" sink: os onDone: function() { if (req.responseCode == HttpStatus.OK) { insert timelineResponse(os) into entries; } else { insert TwitterEntry { content: "Failed to get content\n" "{req.responseMessage}" } into entries; } onDone(entries); } }; req.start(); }
As you can see, this is a straightforward application of the HttpRequest class that we discussed in the first part of this chapter. The XML response is written to a ByteArrayOutputStream, and the onDone function on lines 10 to 20 is called when the whole response has been received. The conversion from XML to TwitterEntry objects is handled by the timelineResponse() function, which we'll discuss shortly, and then the callback function that is passed to getInformITTimeline() is called. This function is part of the application itself and can be found in the file javafxdata/TwitterInformITTimeline.fx . It is important to note that the getInformITTimeline() function is called from the user interface in the main thread of the application and returns after initiating the HTTP request to the web service. At this point, of course, no data will have been received. The function that is passed to getInformITTimeline() is called later, again in the main thread of the application, after the web service's response has been received and parsed.
Parsing the Web Service Response We use the stream parsing API of the PullParser class to parse the XML that we get back from the Twitter web service. Here is a snapshot of a typical response, showing only the parts that we are interested in and with some data removed for the sake of clarity: 1 2 3 4 5 6 7 8
<statuses type="array"> <status> <created_at>Fri May 28 17:24:08 +0000 2010</created_at> <id>14920648241</id> <text>[Text not shown]</text> <user> <id>14347035</id> <name>InformIT</name>
9 10 11 12 13 14 15 16 17 18 19 20
<screen_name>InformIT</screen_name> <location>Indianapolis, IN</location> <profile_image_url> http://a3.twimg.com/profile_images/256118809/ images_normal.jpg </profile_image_url> <created_at>Thu Apr 10 00:29:24 +0000 2008</created_at> <favourites_count>2</favourites_count> <utc_offset>-18000</utc_offset> <time_zone>Indiana (East)</time_zone> </user> </status>
The response consists of a set of <status> blocks, one for each Twitter message. Each of these blocks will be converted to a TwitterEntry object by extracting the values of the following elements: • The <created_at> element shown on line 3, which will become the time value of the TwitterEntry • The <text> element on line 5, which will become the content value • The <screen_name> element within the nested <user> block on line 9, which will be used to set the user value • The <profile_image_url> element, which will become the imageURL value The code in the TwitterClient class that processes the response is shown in Listing 27-5. Listing 27-5 Processing the InformIT Timeline from the Twitter Web Service 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
function timelineResponse (os:ByteArrayOutputStream):TwitterEntry[] { var entries:TwitterEntry[]; var is = new ByteArrayInputStream(os.toByteArray()); var parser = PullParser { input: is documentType: PullParser.XML onEvent: function(evt) { var name = evt.qname.name; if (evt.type == PullParser.START_ELEMENT and name == "status") { user = imageURL = time = content = ""; } else if (evt.type == PullParser.TEXT) { if (name == "screen_name") { user = evt.text; } else if (name == "profile_image_url") { imageURL = evt.text; } else if (name == "created_at" and evt.level == 2) {
19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43
time = evt.text; } else if (name == "text") { content = evt.text; } } else if (evt.type == PullParser.END_ELEMENT) { if (name == "status") { insert TwitterEntry { user: user imageURL: imageURL content: content time: time } into entries; } } } var var var var
user; imageURL; content; time;
} parser.parse(); is.close(); entries }
This function is called with the data retrieved by the HttpRequest class, in the form of a ByteArrayOutputStream. The first thing we need to do, on line 3, is to convert this to a ByteArrayInputStream, because the PullParser class only accepts input in the form of an InputStream. We then create the PullParser object, tell it to expect XML input, and pass it the ByteArrayInputStream containing the data to be parsed. The parsing is handled by the function on lines 7 to 33, which makes use of the user, imageURL, content, and time variables, declared on lines 34 to 37, to hold information that is obtained from the current <status> block. The first event of interest is the START_ELEMENT event for a <status> block, which occurs each time that element is found in the response message. This event, which is handled on lines 9 to 11, resets the user, imageURL, content, and time variables to empty strings. This is not strictly necessary when the first <status> block is seen, but it will be for the remaining blocks. Once we have started processing a <status> block, we need to capture the elements that we listed earlier and save the associated text. On lines 12 to 22, we intercept TEXT events. If the event corresponds to one of the elements of interest, we capture the text and save it in the appropriate variable. Notice that on lines 17 to 18, where we handle the <created_at> element, we explicitly check
that the indentation level is 2. This is necessary because, as you can see from the XML sample shown above, there are two different <created_at> elements in each <status> blockâ&#x20AC;&#x201D;one that is directly inside the block that gives the time at which the Twitter message was posted (see line 4 of the XML snapshot) and one in the nested <user> element that holds the time at which the user's identity was created (see line 13). For this example, we need the first of these two, and we can distinguish it based on its indentation level. Finally, on lines 23 to 32, when the END_ELEMENT event for a <status> block is received, we create a new TwitterEntry object, populating it with the values that we saved while processing the block, and add it to the sequence of TwitterEntry objects that will be delivered to the user interface once all the response has been processed. This completes the implementation of our XML-based Twitter application. As you can see, the facilities provided by the JavaFX platform make it easy to invoke a web service and parse the response into JavaFX objects that can then be displayed to the user. As you'll see in the next section, it is equally easy to interface to a web service that returns a response encoded in JSON.
A JSON Web Service Client JSON (JavaScript Object Notation) is a simple and increasing popular way to encode the results of a web service call. There are several web services that return results either exclusively in JSON format or as an alternative to XML. This section briefly introduces JSON syntax, describes how the PullParser class handles JSON input, and then shows you how to write a JavaFX application that queries a JSON web service.
JSON Syntax and Parsing Listing 27-6 shows an example of JSON syntax. Listing 27-6 A JSON Object Definition 1 2 3 4 5 6 7
{ "name": "Fred", "address": "10 NoStreet, NoPlace", "age": 21, "male": true, "hobbies": ["golf", "reading ", "music"] }
The braces on lines 1 and 7 mark the beginning and end of a JSON object definition, which is composed of name/value pairs. As you can see, a JSON object definition looks very much like a JavaFX object literal, except that the comma separators between pairs are mandatory in JSON, and the names must appear in quotes. Values may be strings (indicated by quotes), integers (see line 4), numbers, booleans (line 5), null, or an array,
an example of which is shown on line 6. Objects can be nested and arrays can be of mixed type, so it is legal to create an array that contains both strings and integers. The PullParser class can parse JSON a document in either pull mode or streaming mode when its documentType variable is set to PullParser.JSON. The events that are reported in JSON mode are different from those used when parsing XML; the event types that are used, all of which are defined by the PullParser class, are listed in Table 27-5. Table 27-5 JSON Parser Event Types Event
Meaning
START_DOCUMENT
Marks the beginning of the JSON document.
START_ELEMENT
The start of a JSON object, corresponding to an opening brace.
START_VALUE
The "name" part of a name/value pair. The name variable of this event contains the actual name. The value itself follows and will itself be followed by an END_VALUE event.
TEXT
Indicates that a string value was encountered.
INTEGER
Indicates that an integer values was found.
NUMBER
Indicates that a floating-point number value was found.
TRUE
The boolean value true.
FALSE
The boolean value false.
NULL
The literal text "null" was given as the value of a name/value pair. The beginning of a JSON array, which occurs when a [ is found.
START_ARRAY
START_ARRAY_ELEMENT The start of an element in an array. END_ARRAY_ELEMENT The end of an element in an array. END_ARRAY
The end of a JSON array, indicated by a ] character.
END_VALUE
Marks the end of processing of a name/value pair. As with the START_VALUE event, the name variable holds the actual name.
END_ELEMENT
The end of a JSON object, corresponding to a closing brace.
END_DOCUMENT
Marks the end of the JSON document.
Here are the events that the parser generates as a result of parsing the JSON document in Listing 27-6: 1 2 3 4
type:7 typeName:START_DOCUMENT level:0 arrayLevel:0 arrayIndex:0 name: text:'' boolean:false integer:0 number:0.0 type:1 typeName:START_ELEMENT level:0 arrayLevel:0 arrayIndex:0 name: text:'' boolean:false integer:0 number:0.0
5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60
type:25 typeName:START_VALUE level:0 arrayLevel:0 arrayIndex:0 name:name text:'' boolean:false integer:0 number:0.0 type:4 typeName:TEXT level:0 arrayLevel:0 arrayIndex:0 name:name text:'Fred' boolean:false integer:0 number:0.0 type:26 typeName:END_VALUE level:0 arrayLevel:0 arrayIndex:0 name:name text:'Fred' boolean:false integer:0 number:0.0 type:25 typeName:START_VALUE level:0 arrayLevel:0 arrayIndex:0 name:address text:'' boolean:false integer:0 number:0.0 type:4 typeName:TEXT level:0 arrayLevel:0 arrayIndex:0 name:address text:'10 NoStreet, NoPlace' boolean:false integer:0 number:0.0 type:26 typeName:END_VALUE level:0 arrayLevel:0 arrayIndex:0 name:address text:'10 NoStreet, NoPlace' boolean:false integer:0 number:0.0 type:25 typeName:START_VALUE level:0 arrayLevel:0 arrayIndex:0 name:age text:'' boolean:false integer:0 number:0.0 type:21 typeName:INTEGER level:0 arrayLevel:0 arrayIndex:0 name:age text:'' boolean:false integer:21 number:0.0 type:26 typeName:END_VALUE level:0 arrayLevel:0 arrayIndex:0 name:age text:'' boolean:false integer:21 number:0.0 type:25 typeName:START_VALUE level:0 arrayLevel:0 arrayIndex:0 name:male text:'' boolean:false integer:0 number:0.0 type:22 typeName:TRUE level:0 arrayLevel:0 arrayIndex:0 name:male text:'' boolean:true integer:0 number:0.0 type:26 typeName:END_VALUE level:0 arrayLevel:0 arrayIndex:0 name:male text:'' boolean:true integer:0 number:0.0 type:25 typeName:START_VALUE level:0 arrayLevel:0 arrayIndex:0 name:hobbies text:'' boolean:false integer:0 number:0.0 type:16 typeName:START_ARRAY level:0 arrayLevel:0 arrayIndex:0 name:hobbies text:'' boolean:false integer:0 number:0.0 type:17 typeName:START_ARRAY_ELEMENT level:0 arrayLevel:0 arrayIndex:0 name:hobbies text:'' boolean:false integer:0 number:0.0 type:4 typeName:TEXT level:0 arrayLevel:0 arrayIndex:0 name:hobbies text:'golf' boolean:false integer:0 number:0.0 type:18 typeName:END_ARRAY_ELEMENT level:0 arrayLevel:0 arrayIndex:0 name:hobbies text:'golf' boolean:false integer:0 number:0.0 type:17 typeName:START_ARRAY_ELEMENT level:0 arrayLevel:0 arrayIndex:1 name:hobbies text:'' boolean:false integer:0 number:0.0 type:4 typeName:TEXT level:0 arrayLevel:0 arrayIndex:0 name:hobbies text:'reading ' boolean:false integer:0 number:0.0 type:18 typeName:END_ARRAY_ELEMENT level:0 arrayLevel:0 arrayIndex:1 name:hobbies text:'reading ' boolean:false integer:0 number:0.0 type:17 typeName:START_ARRAY_ELEMENT level:0 arrayLevel:0 arrayIndex:2 name:hobbies text:'' boolean:false integer:0 number:0.0 type:4 typeName:TEXT level:0 arrayLevel:0 arrayIndex:0 name:hobbies text:'music' boolean:false integer:0 number:0.0 type:18 typeName:END_ARRAY_ELEMENT level:0 arrayLevel:0 arrayIndex:2 name:hobbies text:'music' boolean:false integer:0 number:0.0 type:19 typeName:END_ARRAY level:0 arrayLevel:0 arrayIndex:0 name:hobbies text:'' boolean:false integer:0 number:0.0
61 62 63 64 65 66
type:26 typeName:END_VALUE level:0 arrayLevel:0 arrayIndex:0 name:hobbies text:'' boolean:false integer:0 number:0.0 type:2 typeName:END_ELEMENT level:0 arrayLevel:0 arrayIndex:0 name: text:'' boolean:false integer:0 number:0.0 type:8 typeName:END_DOCUMENT level:0 arrayLevel:0 arrayIndex:0 name: text:'' boolean:false integer:0 number:0.0
As you can see, parsing begins with a START_DOCUMENT event and ends with an END_DOCUMENT event. The START_ELEMENT and END_ELEMENT events on lines 3 and 64 correspond to the beginning and end of the object definition on lines 1 and 7 of Listing 27-6, respectively. Between these two events are the events that report the name/value pairs that make up the object definition. For a simple name/value pair, the parser generates a START_VALUE event that gives the name, an event that reports the actual value, and an END_VALUE event. For example, the name/value pair on line 2 of Listing 27-6 results in the following events: type:25 typeName:START_VALUE level:0 arrayLevel:0 arrayIndex:0 name:name text:'' boolean:false integer:0 number:0.0 type:4 typeName:TEXT level:0 arrayLevel:0 arrayIndex:0 name:name text:'Fred' boolean:false integer:0 number:0.0 type:26 typeName:END_VALUE level:0 arrayLevel:0 arrayIndex:0 name:name text:'Fred' boolean:false integer:0 number:0.0
In this case, the value was a string, so it generates a TEXT event, whereas the value on line 21 of Listing 27-6, which is an integer, generates an INTEGER event: type:25 text:'' type:21 text:'' type:26 text:''
typeName:START_VALUE level:0 arrayLevel:0 arrayIndex:0 name:age boolean:false integer:0 number:0.0 typeName:INTEGER level:0 arrayLevel:0 arrayIndex:0 name:age boolean:false integer:21 number:0.0 typeName:END_VALUE level:0 arrayLevel:0 arrayIndex:0 name:age boolean:false integer:21 number:0.0
Notice that an integer value is held in the integer variable of the Event class, while text is stored in the text variable. The Event class has several variables specific to JSON processing. These variables, together with the ones shared with the XML parser, are listed in Table 27-6. Table 27-6 Variables of the Event Class That Are Relevant to JSON Parsing Variable
Type
type
Integer R
Access
The event type, such as Pull-
Description
typeName
String
The event type in human-readable form
level
Integer R
The nesting level of the current element, starting from 0
name
String
R
The name of the current element
text
String
R
The value of a TEXT element
Parser.START_ELEMENT R
booleanValue integerValue
Boolean R Integer R
numberValue
Number
arrayIndex
Integer R Integer R
arrayLevel
R
The value of a TRUE or FALSE element The value of an INTEGER element The value of a NUMBER element Index of the current element in a JSON array Nesting depth of the current element in a JSON array
When an array is encountered, events are generated for the start and end of the value, the start and end of the array, the start and end of each element in the array, and for each individual array element. Refer to lines 31 to 62 of the parsing output above for the events generated for the array on line 6 of Listing 27-6.
The JSON Weather Service Having seen how the parser handles JSON input, let's look at a real-world exampleâ&#x20AC;&#x201D;the airport weather web service provided at geonames.org, a description of which you can find at http://www.geonames.org/export/JSON-webservices.html. As with the Twitter user timeline service that we used in our earlier example, this service is free and does not require you to register for an API key to make use of it. To get the weather report for an airport, all you have to do is make a request to the URL http://ws.geonames.org/weatherIcaoJSON, supplying the ICAO code of an airport as a parameter. The ICAO (Internal Civil Aviation Organization) code is a unique fourcharacter abbreviation used by pilots and air traffic controllers, among others. It is not the same as the name that appears on your baggage tag, which is an IATA (International Air Transport Association) code, which, unlike the ICAO code, is usually closely related to the actual name of the airport concerned. The ICAO codes for all registered airports can be found in a PDF document on the ICAO website at http://www.icao.int/anb/aig/taxonomy/r4cdlocationindicatorsbystate.pdf. Some commonly used examples are KJFK (New York's Kennedy Airport), KLAX (Los Angeles International), KDCA (Washington Reagan), EGLL (London Heathrow), and EGKK (London Gatwick). Here's a typical response from this web service, reporting the weather at KDCA: { "weatherObservation": { "clouds":"few clouds", "weatherCondition":"n/a", "observation":"KDCA 081652Z 16007KT 10SM FEW055 BKN070 BKN250 29/17 A3020 RMK AO2 SLP225 T02890167", "windDirection":160, "ICAO":"KDCA", "seaLevelPressure":1022.5, "elevation":18, "countryCode":"US", "lng":-77.0333333333333,
"temperature":"28.9", "dewPoint":"16.7", "windSpeed":"07", "humidity":47, "stationName":"Washington DC, Reagan National Airport", "datetime":"2009-08-08 16:52:00", "lat":38.85 } }
This JSON object contains a single weather report for KDCA, with fields that describe the cloud condition, temperature, wind speed and direction, and so on. In the rest of this section, you'll see how to invoke the web service from JavaFX code and parse the result into a form that can be displayed to the user. The application that we're going to create is shown in Figure 27-4.
Figure 27-4 Displaying airport weather reports
You'll find the code that creates the simple user interface for this application in the file javafxdata/JSONWeather.fx. We're not going to discuss this code in any detail. Instead, we'll concentrate on the details of the interaction with the web service itself, which you'll find in the file javafxdata/JSONWeatherClient.fx.
Invoking the JSON Weather Service The code to invoke the weather web service is shown in Listing 27-7. Listing 27-7 Getting Information from the JSON Weather Service 1 2 3 4 5 6 7 8
public function getWeather(place:String, onDone:function(String)){ var os = new ByteArrayOutputStream(); var req = HttpRequest { location: "http://ws.geonames.org/weatherIcaoJSON?ICAO={place}" sink: os onDone: function() { onDone(processJSONResponse(place, os));
9 10 11 12
} } req.start(); }
The parameters to the getWeather() function are the ICAO code of an airport and a function to be called when the web service response has been received and parsed. Unlike the Twitter client example that you saw earlier in this chapter, we don't convert the response into a JavaFX object with variables for the various parts of the response that the caller might be interested in. Instead, for the sake of simplicity and to illustrate a different response, we simply create a summary of the weather that will be displayed as is in the user interface. To initiate the request for the weather, we create an HttpRequest object and initialize its location variable with a URL that the web service interprets as a request for a weather report. Notice that the URL, constructed on line 5, includes a parameter whose value is the ICAO code of the airport. Here's the URL that would used to get the weather for Washington Reagan airport: http://ws.geonames.org/weatherIcaoJSON?ICAO=KDCA
The result is written to a ByteArrayOutputStream and, on completion, the parsing code in the function processJSONResponse() will be called. Apart from the URL, this code is almost identical to that used to get the XML-based Twitter user timeline, shown in Listing 27-4. The major difference between these two examples is, of course, in the parsing code.
Parsing the Weather Service Response To parse the response from the weather service, we create a PullParser, set its documentType variable to PullParser.JSON, install an event handler, and call the parse() function. Listing 27-8 shows the details. Listing 27-8 Parsing the Response from the JSON Weather Service 1 2 3 4 5 6 7 8 9 10 11 12
function processJSONResponse(place:String, os:ByteArrayOutputStream):String { var is = new ByteArrayInputStream(os.toByteArray()); var country = "(Unreported)"; var clouds = "(Unreported)"; var windDir = "(Unreported)"; var windSpeed = "(Unreported)"; var temp = "(Unreported)"; var stationName = "(Unreported)"; var time = "(Unreported)"; var parser = PullParser { input: is
13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47
documentType: PullParser.JSON onEvent: function(evt) { var type = evt.type; var name = evt.name; if (type == PullParser.TEXT) { if (name == "countryCode") { country = evt.text; } else if (name == "clouds") { clouds = evt.text; } else if (name == "windSpeed") { windSpeed = evt.text; } else if (name == "temperature") { temp = evt.text; } else if (name == "stationName") { stationName = evt.text; } else if (name == "datetime") { time = evt.text; } } else if (type == PullParser.INTEGER) { if (name == "windDirection") { windDir = "{evt.integerValue}"; } } } } parser.parse(); is.close(); "Weather for airport {place} ({country})\n" "Time: {time}\n" "Station name is {stationName}\n" "Wind {windSpeed} knots from {windDir}\n" "Temperature {temp}C\n" "Cloud condition: {clouds}\n" }
The variables on lines 4 to 10 will hold the values that are extracted from the response. Because values are sometimes omitted, the variables are all initialized with the string "(Unreported)" so that it is clear to the user that information was not provided. The event handling function on lines 15 to 36 handles both TEXT and INTEGER events. This is necessary because some of the weather details are returned in string form and
others in integer form.10 The code simply checks the name associated with each event and stores the value in the appropriate variable. Finally, when the document has been completely parsed, the information that has been received is used to create the message that will appear in the user interface.
RSS and Atom Feeds A large and increasing number of information sources now provide their content via a feed to which a user can subscribe, in addition to more traditional publication methods such as a website or an email distribution list. JavaFX has support for the two most popular feed protocolsâ&#x20AC;&#x201D;RSS (Really Simple Syndication) and Atom. You can find documentation for the RSS protocol at http://www.rssboard.org/rss-specification and for Atom at http://www.ietf.org/rfc/rfc4287.txt. Both Atom and RSS are XML-based protocols. Access to feeds is usually provided over HTTP, so it is possible to read or subscribe to the content of a feed from a web browser or from a stand-alone application that may display either a single feed or a combination of feeds. In this section, we'll show you how to write readers for both RSS and Atom feeds in JavaFX. Because feed data is encoded in XML, it is possible to use the HttpRequest class to get the content of a feed and the PullParser class to parse it, but this is not necessary because the classes in the javafx.data.feed.rss and javafx.data.feed.atom packages take care of the details for you. All you have to do is supply the URL of the feed source, and the data will be retrieved, parsed, converted to JavaFX objects, and delivered to your application.
Feeds Overview You get an RSS feed by using the RssTask class and an Atom feed from the AtomTask class. Both of these classes are derived from the class javafx.data.feed.FeedTask, which provides the common variables and behavior for both feed implementations. The variables of the FeedTask class are listed in Table 27-7. The location variable gives the URL from which the feed is to be obtained. Most feed sources extract all the information that they need from the URL, but in some cases it is possible to configure the feed source by supplying further information in the form of HTTP headers. For example, the Twitter search API allows more requests per hour to a client that supplies the User-Agent header than to one that does not. You can specify the headers to be send by setting the headers variable. Table 27-7 Variables of the FeedTask Class
10
In JSON terms, a value enclosed in quotes is reported as TEXT even if it looks like a number, so
"23" is text, but 23 is an integer and is reported with an INTEGER event.
Variable
Type
Access Default Description
location
String
RI
The URL of the feed source
interval
Duration
RI
The time between successive polls for content updates
headers
HttpHeader[]
RI
null
The HTTP headers to be sent when requesting the feed
onException funcRW tion(:Exception) :Void
null
Function to be called if an exception occurs while fetching the feed content
onForeignEvent
null
Function to be called when foreign elements are encountered in the feed content
funcRW tion(:Event):Voi d
The FeedTask class allows you to make a one-off request for the content of a feed or to subscribe and have the content automatically updated periodically. You choose the mode of operation that you want by using one of the following functions to fetch the feed content: • The update() function fetches and parses the whole feed. • The poll() function fetches and parses only those items in the feed that have changed because the last time the feed was read. • The start() function performs an update() immediately and then repeats the operation with the frequency given by the interval variable of the FeedTask object, until the stop() function is called. Use this function if you want automatic, periodic updates to be delivered to your application (that is, a subscription). The RSS and Atom feed protocols consist of a set of standard XML elements that the RssTask and AtomTask classes parse and convert into equivalent JavaFX objects. The
XML elements and the JavaFX objects to which they are converted are feed-dependent and are discussed in the sections that follow. For example, an RSS feed is converted to a Channel object, which contains information about the feed itself, and a set of Entry objects, one for each entry in the feed. Some feeds use additional XML elements that are not part of the protocol definition. This is often done to make it easier for the receiver of the feed to decode and use its content. You'll see an example of this when we create a JavaFX client for the Yahoo! Weather service in the next section. These additional XML elements are referred to as "foreign elements" and are delivered to the onForeignEvent function of the FeedTask. Because the meaning of these elements is unknown to the RSS and Atom parsers, they are simply delivered as a stream of raw XML events.
RSS Feeds
To access an RSS feed, you need to initialize an instance of the RssTask class and invoke its start(), update(), or poll() function, depending on whether you want to create a subscription or obtain a single snapshot of the feed's content. The RssTask class adds three variables, which are listed in Table 27-8, to those of the FeedTask class. Table 27-8 Variables of the RssTask Class Variable
Type
Default
Description
onChannel
funcRW tion(:Channel):Void
Access
null
onItem
function(:Item):Void
RW
null
Function to which the feed's Channel object is delivered Function to which Item objects that hold the feed's content are delivered
factory
Factory
R
Class used to convert the XML content of the feed to the corresponding JavaFX objects
The easiest way to explain how an RSS feed works is to look at an example. This section shows you how to create an application that displays information from the Yahoo! Weather feed. Figure 27-5 shows the user interface for this application.
Figure 27-5 The Yahoo! RSS Weather feed
The Yahoo! RSS Weather Feed This Yahoo! Weather feed provides the current weather conditions and a weather forecast for many thousands of locations worldwide. To get the weather for a particular place, you make a request to the following URL: http://weather.yahooapis.com/forecastrss?p=place
The place value in the URL is not a real place name, but an identifier that you can obtain for any given location by looking it up on the Yahoo! Weather page at http://weather.yahoo.com. If you visit this page and ask for the weather for Orlando, Florida, you will be redirected to a page with URL http://weather.yahoo.com/forecast/USFL0372.html. This tells us that the location identifier for Orlando is USFL0372, so the URL for the Orlando weather feed is http://weather.yahooapis.com/forecastrss?p=USFL0372. If you make an HTTP request to this URL, you'll get back a response like that shown in Listing 27-9 (where some of the content has been omitted for the sake of clarity). Listing 27-9 Data from the Yahoo! RSS Weather Feed 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34
<?xml version="1.0" encoding="UTF-8" standalone="yes" ?> <rss version="2.0" xmlns:yweather="http://xml.weather.yahoo.com/ns/rss/1.0" xmlns:geo="http://www.w3.org/2003/01/geo/wgs84_pos#"> <channel> <title>Yahoo! Weather - Orlando, FL</title> <link>http://us.rd.yahoo.com/dailynews/rss/weather/Orlando__FL/*ht tp://weather.yahoo.com/forecast/USFL0372_f.html</link> <description>Yahoo! Weather for Orlando, FL</description> <language>en-us</language> <lastBuildDate>Sun, 26 Jul 2009 5:53 am EDT</lastBuildDate> <ttl>60</ttl> <yweather:location city="Orlando" region="FL" country="US"/> <yweather:units temperature="F" distance="mi" pressure="in" speed="mph"/> <yweather:wind chill="76" direction="0" speed="0" /> <yweather:atmosphere humidity="87" visibility="10" pressure="30.07" rising="0" /> <yweather:astronomy sunrise="6:43 am" sunset="8:19 pm"/> <item> <title>Conditions for Orlando, FL at 5:53 am EDT</title> <geo:lat>28.55</geo:lat> <geo:long>-81.38</geo:long> <link> http://us.rd.yahoo.com/dailynews/rss/weather/Orlando__FL/*http:// weather.yahoo.com/forecast/USFL0372_f.html</link> <pubDate>Sun, 26 Jul 2009 5:53 am EDT</pubDate> <yweather:condition text="Fair" code="33" temp="76" date="Sun, 26 Jul 2009 5:53 am EDT" /> <description><![CDATA[ <img src="http://l.yimg.com/a/i/us/we/52/33.gif"/><br /> <b>Current Conditions:</b><br /> Fair, 76 F<BR /> <BR /><b>Forecast:</b><BR />
35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51
Sun - Scattered Thunderstorms. High: 88 Low: 76<br /> Mon - Scattered Thunderstorms. High: 87 Low: 76<br /> <br/> <a href= "http://us.rd.yahoo.com/dailynews/rss/weather/Orlando__FL/*http:// weather.yahoo.com/forecast/USFL0372_f.html">Full Forecast at Yahoo! Weather</a><BR/><BR/> (provided by <a href="http://www.weather.com" >The Weather Chan nel</a>)<br/>]]></description> <yweather:forecast day="Sun" date="26 Jul 2009" low="76" high="88" text="Scattered Thunderstorms" code="38" /> <yweather:forecast day="Mon" date="27 Jul 2009" low="76" high="87" text="Scattered Thunderstorms" code="38" /> <guid isPermaLink="false">USFL0372_2009_07_26_5_53_EDT</guid> </item> </channel> </rss>
An RSS feed contains one <channel> element that describes the feed itself and one or more <item> elements that contain the feed data. In this example, the <channel> element starting on line 5 will be converted to a Channel object that will be delivered to the feed's onChannel function, and the content of the single <item> element on lines 20 to 49, which contains the weather report for Orlando, will be delivered as an Item element to its onItem function. The feed elements in Listing 27-9 that do not have a namespace prefix are defined by the RSS specification, and their content will appear in either the Channel or the Item object. For example, the content of the <title> element on line 6 will be made available in the title variable of the Channel object, while the value of the <guid> element on line 48 will be stored in the guid variable of the Item object. Elements that have a namespace prefix, like the <yweather:forecast> element on line 44, are not part of the RSS specificationâ&#x20AC;&#x201D;they are extensions used by the Yahoo! Weather service to provide additional information or to make it easier for nonbrowser clients to extract information from the feed. The content of these foreign elements is not available from either the Channel or Item objects; instead, their XML representation is parsed and passed to the onForeignEvent function of the Channel object. When an RSS feed is read by a browser, it uses certain elements to construct a pageâ&#x20AC;&#x201D; the <title> element of the channel is typically used to get the page title, and the <description> element of each item is the useful information that the user will see. For this reason, feed sources often encode the content of the <description> element in HTML so that it can be directly displayed by the browser. The Yahoo! Weather service uses this technique to provide a summary of the weather, complete with a small image that represents the associated weather conditions (sunny, cloudy, raining) and so on, as you can see on lines 30 to 43 of Listing 27-9. Although this is convenient for browser
clients, it is not very useful to an application that does not handle HTML markup, such as our example feed client. The foreign elements in the weather feed are there for the benefit of applications such as our JavaFX weather feed client. If you scan through the foreign elements in Listing 27-9, you'll find that most of the information that is shown in our application's user interface (refer back to) can be obtained from these foreign elements. Here's a list of the information that we need and the elements in which it be found: • The name of the location to which the report applies and the time of the report. This is contained in the item's <title> element on line 21 of Listing 27-9. • An image that represents the current weather. Unfortunately, this is only available from the <description> element of the feed item. To get it, we need to locate the HTML <img> tag shown on line 31 and extract the image URL from it. • A summary of the current weather conditions. This is supplied by the text attribute of the <yweather:condition> element on line 28 of Listing 27-9. Like the remaining elements in this list, this is a foreign element. • The temperature, which comes from the temp attribute of the <yweather:condition> element. • The wind speed and direction. This is provided by the speed and direction attributes of the <yweather:wind> element on line 16. • The forecast for the next day's weather, which we get from the <yweather:forecast> element on line 46. Notice that there are actually two such elements, and we need the one that describes tomorrow's weather. In practice, for the sake of simplicity, we accept whichever of these elements appears last in the feed. From this information, we create an RSSWeather object, which is shown here and defined in the file javafxdata/RSSWeatherFeedClient.fx: public class RSSWeather { public-read var title:String; public-read var conditions:String; public-read var temp:String; public-read var wind:String; public-read var forecast:String; public-read var imageURL:String; }
Fetching and Decoding the Feed As with the previous feed-related examples in this chapter, the source code for the user interface is separate from the code that deals with the feed itself. In fact, we are not going to discuss the user interface code at all, except to say that it uses an instance of the InformationNode class that you saw in the Twitter user timeline example to display the content of an RSSWeather object created from the feed data. As you can see if you look back at Figure 27-5, there is also a TextBox that allows you to enter the location code of
the place for which you want to see the weather. If you want to run this example or look at the source code for the user interface, you'll find it in the file javafxdata/RSSWeatherFeed.fx. The code that fetches and handles the feed data can be found in a function called getRSSWeather() in the file javafxdata/RSSWeatherFeedClient.fx, which is shown in Listing 27-10. Listing 27-10 Handling the Yahoo! RSS Weather Feed 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37
var task:RssTask; public function getRSSWeather(place:String, onDone:function(RSSWeather)) { var weather = RSSWeather {}; var params = Pair { name: "p" value: "{place}" }; var converter = URLConverter {}; var urlParams = converter.encodeParameters(params); if (task != null) { task.stop(); } task = RssTask { location: "http://weather.yahooapis.com/forecastrss?{urlParams}" interval: 60s onItem: function(item) { weather.title = item.title; var description = item.description; var index = description.indexOf("img src="); if (index != -1) { var startIndex = index + 9; var endIndex = description.indexOf('"', startIndex); weather.imageURL = description.substring(startIndex, endIndex); } } onForeignEvent: function(evt) { if (evt.type == PullParser.START_ELEMENT) { var name = evt.qname.name; if (name == "wind") { var speed = evt.getAttributeValue("speed"); var direction = evt.getAttributeValue("direction");
38 39 40 41 42 43 44 45 46 47 48 49 50 52 52 53 54 55 56 57 58 59 60 61 62
weather.wind = "Wind: {speed} mph from {direction}"; } else if (name == "condition") { var condition = evt.getAttributeValue("text"); var temp = evt.getAttributeValue("temp"); weather.conditions = condition; weather.temp = "{temp}F"; } else if (name == "forecast") { var day = evt.getAttributeValue("day"); var date = evt.getAttributeValue("date"); var high = evt.getAttributeValue("high"); var low = evt.getAttributeValue("low"); var text = evt.getAttributeValue("text"); weather.forecast = "Forecast for {day} " "{date} - {text}, high {high}F," "low {low}F"; } } } onDone: function() { onDone(weather); } } task.start(); }
Following the same pattern as our earlier examples, this function requires the location code and a callback function that is used to deliver the RSSWeather object that is created from the feed content. This object is created on line 5, and its variables are populated as the corresponding events are received from the RssTask. The RssTask itself is created on line 15 and a reference to it is held in a script variable. This is necessary, because we subscribe to the feed by setting its interval variable to request that the feed be polled for updates every 60 seconds, until such time as its stop() function is called. If the user changes the location in the user interface, this function will be called again, and we need to remove the previous subscription. We do this by calling the stop() function on lines 11 to 13. As you saw in our discussion of the feed itself, we need to extract some of the information that we need from the <item> element and the rest from three foreign elements. We therefore assign functions to the onItem and onForeignEvent variables so that we are notified when these elements are received. Notice that, unlike our previous examples, we don't use an HttpRequest to fetch the data and a PullParser to process itâ&#x20AC;&#x201D;all of this is taken care of by the RssTask object. Like HttpRequest, RssTask is (indirectly)
derived from javafx.async.Task, so it does most of its work in the background and runs its callback functions in the main thread. The code in the onItem function is straightforward. It receives an argument of type Item that contains the result of parsing the content of the <item> element of the feed and extracts values from the title and description variables. The code that handles the description value, on lines 19 to 30 of Listing 27-10, is nontrivial because it needs to extract the URL of the weather image from the embedded HTML <img> tag. The code in the onForeignEvent function is different from that in onItem because it receives events from the XML parser that the RssTask uses to process the feed data, instead of JavaFX objects created from the parser output. Because the values that we are interested in are carried in attributes of the <wind>, <condition>, and <forecast> elements, we intercept the START_ELEMENT event for each of the elements and use its getAttributeValue() function to get the required values, and then store them in the corresponding variables of the RSSWeather object. Finally, when the feed data has been processed, the RssTask object calls the onDone function, which in turn delivers the RSSWeather object to the callback passed to getRSSWeather() function. This whole process will be repeated automatically every 60 seconds because we set the interval variable of the RssTask object and subscribed to the feed by calling its start() function. If you want a one-time capture of the feed, you should use the update() function instead.
Atom Feeds The Atom feed protocol is newer and more complex than RSS, but for the JavaFX developer, it is just as easy to write an application that works with an Atom feed as it is to handle an RSS feed. You connect to an Atom feed by using the javafx.data.feed.atom.AtomTask class, which, like RssTask, derives from FeedTask. Whereas an RSS feed consists of one or more <item> elements nested inside a <channel> element, in Atom the feed itself is represented by a <feed> element, which is converted to a Feed object, and the individual items in the feed are contained in <entry> elements, which become Entry objects. The AtomTask class has two callback variables, listed in Table 27-9, that are used to deliver Feed and Entry objects to application code. Table 27-9 Variables of the AtomTask Class Variable
Type
onFeed
function(:Feed):Void RW
null
Function to which the feed's Feed object is delivered
onEntry function(:Entry):Void RW
null
Function to which Entry objects that hold the feed's content are delivered
factory Factory
Access Default
R
Description
Class used to convert the
XML content of the feed to the corresponding JavaFX objects
The Twitter Search Feed Twitter has a search API that returns its results in either JSON or Atom format. It is convenient to use this API for an example of an Atom feed because the information that it contains is similar to that in the user timeline, which means we can reuse the TwitterEntry class that we used to encapsulate the timeline data earlier in this chapter, and we can also reuse the user interface code with only a minor change, namely the addition of a TextBox that allows you to enter a search term. We are not going to discuss the user interface code in this section because our focus is on how to work with the feed, but if you'd like to look at it or run the example, you'll find it in the file javafxdata/TwitterAtomSearch.fx. To perform a Twitter search based on a keyword or keywords, you make a request to a URL that looks like this: http://search.twitter.com/search.atom?q=javafx&rpp=50
The q= parameter specifies the keyword or keywords for the search, and the rpp= parameter is the maximum number of results to return (that is, results per page). You can see part of a typical response to this query in Listing 27-11, which shows the <feed> element and the first of the 50 <entry> elements from the reply, with some of the data anonymized. Listing 27-11 The Twitter Search Atom Feed 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
<feed xmlns:google="http://base.google.com/ns/1.0" xml:lang="en-US" xmlns:openSearch="http://a9.com//spec/opensearch/1.1/" xmlns=http://www.w3.org/2005/Atom xmlns:twitter="http://api.twitter.com/"> <id>tag:search.twitter.com,2005:search/javafx</id> <link type="text/html" rel="alternate" href="http://search.twitter.com/search?q=javafx"/> <link type="application/atom+xml" rel="self" href="http://search.twitter.com/search.atom?q=javafx&amp;rpp=10"/> <title>javafx - Twitter Search</title> <link type="application/opensearchdescription+xml" rel="search" href="http://search.twitter.com/opensearch.xml"/> <link type="application/atom+xml" rel="refresh" href="http://search.twitter.com/search.atom?q=javafx&amp;rpp=10 &amp;since_id=3250464807"/> <twitter:warning>since_id removed for pagination. </twitter:warning> <updated>2009-08-11T19:24:26Z</updated>
19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46
<openSearch:itemsPerPage>10</openSearch:itemsPerPage> <link type="application/atom+xml" rel="next" href="http://search.twitter.com/search.atom?max_id=3250464807&amp; page=2&amp;q=javafx&amp;rpp=10"/> <entry> <id>tag:search.twitter.com,2005:3250464807</id> <published>2009-08-11T19:24:26Z</published> <link type="text/html" rel="alternate" href="http://twitter.com/tiainen/statuses/3250464807"/> <title>Installing #netbeans 6.7.1 so i can continue my #javafx adventures.</title> <content type="html">Installing &lt;a href="http://search.twitter.com/search?q=%23netbeans"&gt;#netbeans &lt;/a&gt; 6.7.1 so i can continue my &lt;a href="http://search.twitter.com/search?q=%23javafx"&gt;#&lt;b&gt;j avafx&lt;/b&gt;&lt;/a&gt; adventures.</content> <updated>2009-08-11T19:24:26Z</updated> <link type="image/png" rel="image" href="http://s3.amazonaws.com/twitter_production/profile_images/46 626732/2_normal.gif"/> <twitter:source>&lt;a href= data removed/a&gt;</twitter:source> <twitter:lang>en</twitter:lang> <author> <name>removed</name> <uri>removed</uri> </author> </entry>
As you can see, there is a mixture of elements with and without namespace prefixes. As with the RSS feed, the elements without a namespace prefix are defined by the Atom specification, while those with namespace prefixes are foreign elements meant to be interpreted by a nonbrowser client specifically written to handle this feed. You can see how each of the standard elements is mapped to variables of the Feed and Entry classes by consulting their API documentation. For our example application, we need to extract from each Atom Entry the information necessary to construct a TwitterEntry object. The values that we need are all available from elements that are defined by the Atom specification itself, so unlike the RSS example in the previous section, we don't need to provide code to extract information from foreign elements. The values that we need are as follows: â&#x20AC;˘ The name of the entry's author, which will be used to set the user variable of the TwitterEntry object. This value is contained in the <author> element, which you'll find on lines 42 to 45 of Listing 27-11. An Atom entry can have more than one
author, so the authors variable in the Entry object is actually a sequence. As you'll see later, we use the first element in the sequence as the user value. • The entry content, which is taken from the <title> element on lines 28 and 29 of Listing 27-11. We use the <title> value rather than the content of the more obvious <content> variable on line 30 because the latter contains the same text, but also has HTML markup, which we do not want the user of our application to see. The <title> value is used to set the content variable of the TwitterEntry object. • The publication time of the entry, which we will use to set the time variable of the TwitterEntry object. We get this from the <published> element shown on line 25 of Listing 27-11, which becomes the value of the published variable of the feed's Entry object. • The URL of the user's image, which you'll find in the <link> element on line 36. There is more than one <link> element in this feed entry—you'll find another one on line 26. All the <link> elements are gathered into a sequence of Link objects and stored in the links variable of the feed's Entry object. As you'll see later, we need to search this sequence to locate the link that refers to the image,
Fetching and Decoding the Feed The code that gets the feed content and converts it to TwitterEntry objects is shown in Listing 27-12 and can be found in the file javafxdata/TwitterAtomSearchClient.fx. Listing 27-12 Handling the Twitter Search Feed 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
public function doTwitterAtomSearch(query:String, count:Integer, onDone:function(TwitterEntry[]):Void) { var params = [ Pair { name: "q" value: query }, Pair { name: "rpp" value: "{count}" } ]; var converter = URLConverter {}; var urlParams = converter.encodeParameters(params); var task = AtomTask { location: "http://search.twitter.com/search.atom?{urlParams}" var entries:TwitterEntry[]; onEntry: function(entry) { var user = entry.authors[0].name; var content = entry.title.text; var time = new Date(entry.published.datetime.instant).toString(); var imageURL = ""; for (link in entry.links) { if (link.rel == "image") {
21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37
imageURL = link.href; break; } } insert TwitterEntry { user: user content: content time: time imageURL: imageURL } into entries; } onDone: function() { onDone(entries); } } task.update(); }
The doTwitterAtomSearch() function requires a query string, the number of results to be returned, and the function to be called when the results are available. The structure of this function should, by now, be familiar. On lines 3 to 8, we use the Pair and URLConverter classes that were discussed earlier in this chapter to create and encode the parameter string, which will contain the query and the number of results per page. For the query string "Cessna 172" and a maximum of 50 results, the query string would look like this: q=Cessna+172&rpp=50
Lines 10 to 35 create the AtomTask object that will connect to the feed, parse the response, and deliver the resulting Entry objects to the onEntry function defined on lines 13 to 31. We do not supply an onFeed function because we have no use for the feed information in this example. If you compare this code with the RSS feed code in Listing 27-10, you'll notice that we do not assign a value to the interval variable, and we use the update() function (on line 36) to access the feed instead of the start() function. This is because you typically don't want the results of a search to update automatically, so instead of a subscription, we make a one-time request for a snapshot. The feed data is handled on lines 13 to 31. As you can see, because all the information we need is contained in the Entry object, we can simply extract it and use it to create a TwitterEntry object. Most of the code is straightforward, but two points are worth noting: â&#x20AC;˘ The publication date is obtained from the publication variable, which is of type javafx.date.DateTime . This class holds a single value (in a variable called instant ) that represents the date and time as a number of milliseconds since 00:00 on Jan 1, 1970. In the desktop profile, you could use the java.text.DateFormat class
to convert this to a date and time string, but this class is not available on MIDPbased devices, so for the sake of portability, we use the time value to create a java.util.Date object and then use its toString() method to get a string representation of the date and time. • As mentioned earlier, an <entry> element may contain any number of <link> elements that refer to related data of different types. In Listing 27-11, there are two <link> elements, one that refers to an HTML page and the other to the user's image, which is the information that we need. The AtomTask class converts the <link> elements to a sequence of javafx.data.feed.atom.Link objects. To find the URL of the user's image, we search through this sequence until we find a Link object for which the rel variable has value image. The code that does this is shown on lines 18 to 24. If there is no such link, the space allocated for the user's image in the user interface will be blank.
Tasks and Progress Monitoring Three of the classes that you have seen in this chapter—HttpRequest, RssTask, and AtomTask— perform asynchronous operations in a background thread while reporting their status and delivering results in the main application thread. These classes all derive from javafx.async.Task, which is intended to be the basis for all asynchronous operations on the JavaFX platform. As you'll see in the next section, this class can be used to implement your own background operations. In this section, we look at the features of the Task class that allow you to monitor and control the state of an asynchronous operation.
Task State Variables The common state of an asynchronous operation is held in the variables of the Task class, which are listed in Table 27-10. Subclasses may add additional state, as appropriate. For example, the HttpRequest class adds variables that indicate whether data is currently being read or written. Table 27-10 Variables of the Task Class Variable
Type
onStarted
function():Void RW
Access Default
null
Function called when the task is about to start.
onDone
function():Void RW
null
Function called when the task has completed (successfully or otherwise).
started
Boolean
R
Description
Indicates whether the task has been started.
stopped
Boolean
true when the task has
R
been stopped by calling the
stop() function. succeeded
Boolean
R
failed
Boolean
R
done
Boolean
R
Indicates that the task ran to a successful completion. true if the task failed to complete normally. Indicates that the task is no longer running.
causeOfFailure Object
R
null
A value that indicates the reason why the task failed, if this is known. This will typically be a Throwable.
progress
Number
R
-1
The progress made by the task so far, or -1 if the task is not yet started or the progress made is not known.
progressMax
Number
R
-1
The value of progress that would indicate that the task is complete, or -1 if the task is not yet started or the progress made is not known.
percentDone
Number
R
-1
The progress made so far as a proportion of the maximum. The value will be in the range 0 to 1, or -1 if the operation is not started or if the progress made or maximum progress value is not known. The variables started, stopped, succeeded, failed, and done describe the current state of the operation. Initially, all these variables are false. When the start() function is called, the started variables is set to true and remains in that state, even when the operation has completed. You cannot, therefore use the started variable on its own to determine whether the operation is in progress. When the operation ends, the done variable is always set to true, so the correct test for whether the operation is in progress is
as follows: (started and not done).
An operation can terminate for any of three reasons: â&#x20AC;˘ The stop() function has been called. In this case, the stopped, failed, and done variables are all set to true.
• The operation fails. In this case, a message giving the reason for the failure may be stored in the causeOfFailure variable, and the failed and done variables will be set to true. • The operation succeeds, in which case both the succeeded and done variables will be set to true. Note that because of the way these variables are managed, it is possible for an operation to be simultaneously started and stopped or started and done. The values of these variables are typically used to enable or disable user interface controls whose state should depend on that of the operation with which they are connected. You'll see an example of this in Listing 27-13 later in this section. Some state changes are also reported by a callback function—the onStarted function is called just before the operation is initiated, and the onDone function is invoked just after it terminates. You have already seen examples in this chapter that use the onDone function to do things such as trigger parsing of XML that has been read by an HttpRequest.
Progress Monitoring The progress and progressMax variables allow a task to periodically report how much of an operation has been performed. If a task chooses to report progress, the Task class will maintain a third variable—percentDone—that represents the proportion of the operation that has been completed. In the case of an operation that has just begun and not yet made any progress, the percentDone variable would be 0, while for one that has succeeded it would have the value 1. It is important to note the following if you plan to use these variables: • Task implementations are not required to report progress at all. In this case, all three variables will have the value -1, which is conventionally interpreted to mean that the amount of progress made is unknown. • A task that reports progress must set both the progress and progressMax variables before the value of the percentDone variable will be set. If either of these variables is subsequently changed (which is very likely in the case of progress but less likely for progressMax), the value of percentDone will be recalculated accordingly.
A State Monitoring Example The HttpRequest class correctly maintains the state variables that it inherits from the Task class and also reports progress as it reads or writes data. The code in Listing 27-13 creates an HttpRequest to read a PDF version of the Java Language Specification from Sun Microsystems' website and a simple user interface that monitors that lets you see the progress that is reported. It also reports the state of the started, stopped, succeeded, failed, and done variables on the console. You'll find this code in the file javafxdata/TaskExample1.fx.
Listing 27-13 Displaying the State of an HttpRequest Operation 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44
var r:HttpRequest = HttpRequest { location: "http://java.sun.com/docs/books/jls/download/" "langspec-3.0.pdf"; sink: new ByteArrayOutputStream() }; Stage { var scene:Scene; title : "Task Example #1" scene: scene = Scene { width: 350 height: 200 content: [ VBox { width: bind scene.width height: bind scene.height spacing: 16 vpos: VPos.CENTER hpos: HPos.CENTER nodeHPos: HPos.CENTER content: [ Text { content: bind "Read {r.progress} of {r.maxProgress} " "({100 * r.percentDone}%)" } ProgressBar { progress: bind if (r.percentDone == -1) 0 else r.percentDone; } Text { wrappingWidth: bind scene.width content: bind if (r.stopped) "Stopped" else if (r.succeeded) "Complete" else r.causeOfFailure.toString() } Button { text: "Press to start..." disable: bind r.started action: function() { r.start(); } } Button { text: "Press to stop..."
45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67
disable: bind not r.started or r.done action: function() { r.stop(); } } ] } ] } } var started = bind r.started on replace { println("started: {started}") }; var stopped = bind r.stopped on replace { println("stopped: {stopped}") }; var succeeded = bind r.succeeded on replace { println("succeeded: {succeeded}") }; var failed = bind r.failed on replace { println("failed: {failed}") }; var done = bind r.done on replace { println("done: {done}") }; var causeOfFailure = bind r.causeOfFailure on replace { println("causeOfFailure: {causeOfFailure}"); };
The result of running this code is shown in Figure 27-6.
Figure 27-6 Tracking the progress of a task
The values of the progress, progressMax, and percentDone variables are shown in the Text node created on lines 20 to 24 of Listing 27-13. You can see that the value of progressMax has been set to the total length of the PDF file, while the progress variable is being updated to reflect how much has been read so far. The percentDone value
is automatically recalculated as the value of the progress variable changes. When the application starts, these variables all have the value -1. The value of the percentDone variable is also reflected in the progress bar, which gives a visual representation of how much of the document remains to be read. The two buttons that appear below the progress bar enable you to start and stop the operation by calling respectively the start() and stop() functions of the HttpRequest. The start buttons is enabled only when the operation has not yet been started, while the stop button is enabled only while it is in progress. This is done by linking the disable variable of each button with the state variables of the HttpRequest. Here's the expression that is used to determine the value for the disable variable of the start button is controlled: disable: bind r.started
This makes sense, because the button should be enabled only when the operation has not yet been started. Turning this the other way up, it should be disabled when the operation has been started. The expression required to assign the correct state to the stop button is slightly more complex: disable: bind not r.started or r.done
The easiest way to understand this is to work out when the button should be enabled and then invert the conditionâ&#x20AC;&#x201D;it should be possible to stop an operation if it has been started and it is not yet done. Inverting this, we get the condition shown above for disabling the button. The triggers on lines 57 to 67 of Listing 27-13 let you see the changes to the started, stopped, failed, succeeded, done, and causeOfFailure variables as they occur. Here's the output that results from the normal completion of the HttpRequest: started: true done: true succeeded: true
If the operation failed because the document you are trying to fetch does not exist, you would see the following: started: true failed: true causeOfFailure: java.io.FileNotFoundException: (file name not shown) done: true
Asynchronous Operations and Database Access Most nontrivial applications are likely to need to perform an operation that would take long enough to complete that it could not be allowed to execute in the application's main thread, because to do so would block user interaction and make the application appear to be unresponsive. The classes that you have seen so far in this chapter let you access resources on the Internet without blocking the user interface, but there is (at least at the
time of this writing) no prepackaged solution for asynchronous access to other information sources that are not capable of delivering data immediately, such as databases. In this section, you'll see how to create a JavaFX application that populates a ListView control from the values in a database table. The focus will be on the data access part of the application, which we will perform in a background thread, rather than on how we display the results to the user. If you want to look at the code that creates the user interface, you'll find it in the file javafxdata/TaskExample2.fx. The result of running this code is shown on the left of Figure 27-7.
Figure 27-7 A JavaFX database application
As you can see, the ListView control is initially empty. If you click the Fetch Customers button, there will be a small delay, and then the ListView will be populated with a set of customer names obtained from a database table. The reported progress will also change from 0 to 100, as shown on the right of Figure 27-7. To run this example, you need to be running a database server that has a table containing the customer data to be displayed in the ListView. The easiest way to achieve this is to install the GlassFish application server, the JavaDB database, and the NetBeans IDE plug-ins needed to access them, as described in the Preface. As part of this installation, a sample database containing the data used by this example will be added to the JavaDB database server. To start the server from with NetBeans, display the Services tab in the IDE and open the Databases node. You should see an entry for a JavaDB database server on your machine, as shown in Figure 27-8. To start the database server, right-click its node, and select Connect from the context menu. After a few seconds, the database will be running and, if you now expand the database node, you'll find the CUSTOMERS table, which will be the source of the data for the example application.
Figure 27-8 Viewing the sample database in the NetBeans IDE
A Database Access Task To access the database, we need to create a class that can be configured with the information needed to locate and connect to a database server, will read the content of the CUSTOMERS table in a background thread, and will finally deliver the data that we need to a callback function in the application's main thread. Here's a sketch of what this class might look like:11 public class FetchCustomersTask extends Task { public-init var dbURL:String; public-init var params:Pair[]; public-read var customers:JavaFXCustomer[]; public var onCustomers:function(JavaFXCustomer[]):Void; }
As you saw in the preceding section, asynchronous operations in JavaFX are based around the Task class, so we use it as the base for this class. In so doing, we inherit functions that can be used to start and stop the task and variables for status reporting (started, stopped, and so forth). The dbURL and params variables are intended to be used to specify the location of the database and the user name and password needed to log in. Having created an instance of the FetchCustomersTask class with these two variables initialized, the application would then call its start() function and let it get on 11
The final result will look very much like this, although it is not identical.
with its job in the background. On completion, the records read from the database would be converted to instances of the JavaFXCustomer class, installed in the customers variable, and then the onCustomers function would be called to notify the application that the results are available. Here is the definition of the JavaFXCustomer class: public class JavaFXCustomer { public-init var customerId:Integer; public-init var name:String; }
Because we are only creating an example, this class is pretty minimal and does not include most of the information from the database tableâ&#x20AC;&#x201D;in fact, the example application uses only the value of the name column. The class is called JavaFXCustomer rather than simply Customer to emphasize that it is a JavaFX class; you'll see shortly why this is a good idea. Having seen the outline of what we are going to implement, let's look at how it will be used. The code in Listing 27-14 shows how the example application uses the FetchCustomersTask to read the CUSTOMERS tableâ&#x20AC;&#x201D;this is the actual code from the finished application. Listing 27-14 Asynchronous Database Access 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
var customers:JavaFXCustomer[]; var task:FetchCustomersTask; function getCustomers() { task = FetchCustomersTask { dbURL: "jdbc:derby://localhost:1527/sample" params: [ Pair { name: "user" value: "app" }, Pair { name: "password" value: "app" } ] onCustomers: function(result):Void { customers = result; } } task.start(); }
The getCustomers() function creates an instance of the FetchCustomersTask object; initializes it with the database URL, username, and password required to log in; and a function, to be called when the customer records have been located, calls the start() function to initiate the operation. The data in the CUSTOMERS table will be read in the background, converted to JavaFXCustomer objects, and installed in the customers
variable of the FetchCustomersTask object, and finally the onCustomers function will then be called. This function copies the returned records into a sequence variable called customers, to which the ListView control is bound, as shown here: ListView { items: bind for (c in customers) { c.name } }
Here's a breakdown of the steps involved in reading from the database and returning the results to the application: 1. A background thread must be created. 2. A connection must be made to the database. 3. The CUSTOMER_ID and NAME columns of each row of the CUSTOMERS table must be read and a JavaFXCustomer object created for each row. 4. The resources used to access the database must be released. 5. The JavaFXCustomer objects must be stored in the customers variable of the FetchCustomersTask object. 6. The onCustomers function of the FetchCustomersTask object must be called. Step 1 is performed in the application's main thread and steps 2 to 4 in the background thread. Step 5 requires the background thread to update the state of the FetchCustomersTask object and finally, step 6 must be executed in the application's main thread. At first glance, you may think that implementing this task is going to be fairly straightforward—it looks like all you need to do is start a background thread, perform steps 2 to 4 in that thread, write the results to the FetchCustomersTask object, and finally call the onCustomers function back in the main thread. However, JavaFX is a single-threaded language, and the compiler and runtime make the following assumptions: • That access to JavaFX variables is made only from the application's main thread. If this were not the case, there would either need to be language-level support for synchronization, or the compiler and runtime would have to cooperate somehow to transparently guarantee thread safety (which they do not). • That JavaFX code is executed only in the application's main thread. The consequence of the first assumption is that the variable updates required in step 5 cannot be made directly from the background thread, and the result of the second assumption is that the code that runs in the background thread has to be written in Java instead of JavaFX. Writing the code that runs in the background thread in Java causes another problem, arising from the fact that in step 3, we have to create a JavaFXCustomer object for each row of the CUSTOMERS table. The problem is that creating a JavaFX object in a back-
ground thread is not allowed. We can solve this problem by creating a Java class that is equivalent to the JavaFXCustomer class, which will have the same variables and which will be called JavaCustomer to make clear that it is a Java class. As you'll see later, the JavaCustomer objects will be converted to JavaFXCustomer objects at step 5 of the process. It follows that we need at least four classes to implement the FetchCustomersTask: • The FetchCustomersTask class itself, which is a JavaFX class • The JavaFXCustomer class, which is another JavaFX class • The class that contains the code that will run in the background thread, which must be a Java class • The JavaCustomer class, which is also a Java class In fact, as you'll shortly see, we also need to create a Java interface that will allow Java code to update the state of the FetchCustomersTask object.
Implementing the Database Access Task The JavaTaskBase class in the javafx.async package is intended to be used as the base class for all asynchronous operations. It derives from Task and adds one additional function, which must be implemented by subclasses: protected abstract function create():RunnableFuture;
This function is required to return the object that contains the code that will be run in the background thread. As we have already seen, this class must be written in Java, and the signature of the create() function requires it to implement the javafx.async.RunnableFuture interface, which consists of a single method: public abstract void run() throws Exception;
If this function returns a JavaFX object, an exception will be thrown. It is, therefore, not possible to implement the background task in JavaFX. The JavaTaskBase class arranges for the create() function to be called and the code in the run() method of the returned Java object to be executed in a background thread. If this method completes normally, it is assumed that the operation succeeded and the succeeded and done variables of the task will be set to true. If it throws an exception, that exception will be stored in the causeOfFailure variable of the task, and the failed and done variables will be set to true. The implementation of the FetchCustomersTask class is shown in Listing 27-15. Listing 27-15 The FetchCustomersTask Class 1 2
public class FetchCustomersTask extends JavaTaskBase, TaskUpdater { public-init var dbURL:String;
3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34
public-init var params:Pair[]; public-read var customers:JavaFXCustomer[]; public var onCustomers:function(JavaFXCustomer[]):Void; protected override function create():RunnableFuture { var props = new Properties(); for (param in params) { props.put(param.name, param.value); } new JavaCustomerFetcher(dbURL, props, this); } // Implementation of TaskUpdater interface public override function setReturnValue(value:Object) { var javaCustomers = value as nativearray of JavaCustomer; for (javaCustomer in javaCustomers) { insert JavaFXCustomer { customerId: javaCustomer.customerId; name: javaCustomer.name; } into customers; } if (onCustomers != null) { onCustomers(customers); } } public override function setProgress(progress:Long, maxProgress:Long) { this.progress = progress; this.maxProgress = maxProgress; } }
As you can see, this class derives from JavaTaskBase. The implementation of the create() function on lines 7 to 13 converts the name/value pairs in the params sequence variable into a Properties object and then creates and returns an instance of the JavaCustomerFetcher class, passing it the URL of the database, the Properties object, and a reference to the task object itself. This reference is needed to allow the background task to indirectly update the customers variable and to update the progressrelated variables of the task. The JavaCustomerFetcher class, which you'll see shortly, is the class that contains the code that will run in the background thread. It is required to implement the RunnableFuture interface.
The remaining two functions in this class are required by the TaskUpdater interface, which is the third Java class in our implementation of this task. These functions are provided to allow the code in the JavaCustomerFetcher class to do the following: â&#x20AC;˘ Return the objects constructed from the content of the CUSTOMERS table by calling the setReturnValue() function. â&#x20AC;˘ Set the progress and maxProgress variables (which are inherited from Task) by calling the setProgress() function. The setReturnValue() function (lines 16 to 27) is called with argument that is actually a Java array of JavaCustomer objects.12 The content of this array is converted to a sequence of JavaFXCustomer objects that is stored in the customers variable and then delivered to the onCustomers callback function, if one has been installed. This function and the setProgress() function on lines 29 to 33 are called from the code running on the background thread, but execute in the main thread of the application, to satisfy the constraint that all JavaFX code must run on this thread. You'll see how this is done when we look at the implementation of the JavaCustomerFetcher class, which is shown in Listing 27-16. Listing 27-16 The JavaCustomerFetcher Class 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
12
class JavaCustomerFetcher implements RunnableFuture { private final String dbURL; private final Properties params; private final TaskUpdater task; JavaCustomerFetcher(String dbURL, Properties params, TaskUpdater task) { this.dbURL = dbURL; this.params = params; this.task = task; } @Override public void run() throws Exception { updateProgress(0, 100); Class.forName("org.apache.derby.jdbc.ClientDriver"); Connection conn = null; Statement stmt = null; List customers = new ArrayList(); try {
The argument to this function is defined to be of type object to allow the TaskUpdater interface
to be used in the implementation of other tasks that need to return a value from the code that runs in the background.
21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65
conn = DriverManager.getConnection(dbURL, params); stmt = conn.createStatement(); ResultSet rs = stmt.executeQuery( "SELECT CUSTOMER_ID, NAME from CUSTOMER ORDER BY NAME"); while (rs.next()) { int id = rs.getInt("CUSTOMER_ID"); String name = rs.getString("NAME"); customers.add(new JavaCustomer(id, name)); } JavaCustomer[] results = new JavaCustomer[customers.size()]; setCustomers( (JavaCustomer[])customers.toArray(results)); rs.close(); } finally { if (stmt != null) { stmt.close(); } if (conn != null) { conn.close(); } } updateProgress(100, 100); } private void updateProgress(final long final long FX.deferAction(new Function0() { @Override public Void invoke() task.setProgress(progress, return null; } }); }
progress, progressMax) { { progressMax);
private void setCustomers(final JavaCustomer[] results) { FX.deferAction(new Function0() { @Override public Void invoke() { task.setReturnValue(results); return null; } }); } }
The principal task of the JavaCustomerFetcher class, which is a Java class, is to read the content of the CUSTOMERS table and convert each row to a JavaCustomer object. The code that handles this task can be found on lines 16 to 44, most of which is standard JDBC code that we won't discuss it in detail. The rest of the code is concerned with returning results to the FetchCustomersTask class, which must be done in the main thread of the application. There are three points at which this code needs to do this: • On lines 14, it reports progress by calling the updateProgress() function. • Progress is again reported on line 44. • On line 32, it calls the setCustomers() function to pass the JavaCustomer objects created from the CUSTOMERS table to the FetchCustomersTask class. In all three cases, the desired action is simply to write a value to a variable in the JavaFX class. However, this is not possible for two reasons: • JavaFX variables can be read and written only from the main thread, and this code is not running in the main thread. • There is no officially supported way for Java code to directly access a JavaFX variable. This is because the way in which JavaFX variables are represented in compiled class files is currently a detail known only to the compiler and the runtime. The only way to read or write JavaFX variables from Java is by calling a getter or setter function in the JavaFX class that can do the job for you. This is why the setReturnValue() and setProgress() functions were included in the FetchCustomersTask class. To update the state of the FetchCustomersTask class, the updateProgress() function must call the setProgress() function of the FetchCustomersTask class, but it cannot do so directly because the call must occur in the application's main thread. To do this, it uses the deferAction() function of the javafx.lang.FX class. This is the same function that was introduced in Chapter 12, "Platform APIs," and which is used to execute a JavaFX function at some future point on the main application thread. Here, we are calling it from a Java class on a different thread. Because Java does not have the concept of function pointers, we cannot directly pass a reference to the setProgress() function. Instead, we have to use the following construct: FX.deferAction(new Function0() { @Override public Void invoke() { task.setProgress(progress, progressMax); return null; } });
Function0 is a class that allows a function with no arguments to be called. The code that does must be placed in the invoke() method, which is overridden in this example to call the setProgress() function of the FetchCustomersTask object.
It might at first seem that implementing an asynchronous task is quite a complex undertaking, but in reality it is simpler than it looks. Much of the discussion in this section has been around how and why the work should be divided between the task itself and the class that contains the code that is to be run in the background, and we have spent a lot of time looking at the details. In practice, if you need to create your own asynchronous task, you can do so by taking the example in this section as a template, removing all the code that is specific to database access, and replacing it with your own code. In future releases of JavaFX, it is likely that support will be added to make it easier for the Java code that runs in the background thread to communicate with the JavaFX class, which would make it possible to simplify or remove much of the code that appears in this example.
CHAPTER 3
Tutorial Introduction
BUY
from informIT and
SAVE 35%
Enter the coupon code JAVAONE10 during checkout.
Google Bookmarks
Delicious
Digg
StumbleUpon
OSGi and Equinox:
Creating Highly Modular Java™ Systems AUTHORS Jeff McAffer, Paul VanderLei, and Simon Archer
FORMATS & ISBNs Book: 9780321585714 eBook: 9780321612359 Safari Books Online: 9780321561510
In OSGI and Equinox: Creating Highly Modular Java™ Systems, three leading experts show developers—for the first time—exactly how to make the most of these breakthrough technologies for building highly modular dynamic systems.
Declarative Services and how to use them to solve a wide variety of real-world problems. Finally, you’ll see everything that you’ve learned implemented in a complete case study project that takes you from early prototype through application delivery.
You’ll quickly get started with Eclipse bundle tooling, create your first OSGi-based system, and move rapidly to sophisticated production development. Next, you’ll master best practices and techniques for creating systems with exceptional modularity and maintainability. You’ll learn all about OSGi’s
For every Eclipse developer, regardless of previous experience, this book • Combines a complete hands-on tutorial, online sample code at every step, and deep technical dives for working developers
• Covers the OSGi programming model, component development, OSGi services, Eclipse bundle tooling, server-side Equinox, and much more • Offers knowledge, guidance, and best practices for overcoming the complexities of building modular systems • Addresses practical issues ranging from integrating third-party code libraries to server-side programming • Includes a comprehensive case study that goes beyond prototyping to deliver a fully refined and refactored production system
informit.com/learnjava
McAffer_OSGi.book Page 31 Monday, January 11, 2010 12:21 PM
CHAPTER 3 Tutorial Introduction This chapter guides you on the journey of developing a fully functional OSGibased application, Toast. Using the same application throughout the book adds to the coherence of the samples and more closely matches the situations encountered in real-world software development. The principles and practices discussed throughout are applicable in a wide range of application domains and execution scenarios. Before getting down to the nuts and bolts of Toast development, we set the stage for the application by outlining its nature and evolution. We also ensure that you are set up with a working development environment. This chapter focuses on three major issues: ❍
Outlining the sample application and sketching its evolution
❍
Setting up your Eclipse IDE so you can develop the code yourself
❍
Getting and using the Samples Manager to compare and manage the sample code
3.1 What Is Toast? Toast is a sample application in the telematics and fleet management domain. If you’re unfamiliar with the term, you’re almost certainly familiar with the concept. Wikipedia has the following to say about telematics: [Telematics is] the integrated use of telecommunications and informatics. More specifically it is the science of sending, receiving and storing information via telecommunication devices. You will have seen this in car navigation and infotainment devices. A typical telematics system interfaces to the devices in the vehicle and provides a user interface 31
McAffer_OSGi.book Page 32 Monday, January 11, 2010 12:21 PM
32
CHAPTER 3 • Tutorial Introduction
for interacting with or managing the devices. More sophisticated systems connect to a fleet management control center over a wireless network and allow remote control of the devices. In its finished form, Toast covers all of these bases—it interfaces to a simulated GPS and airbag, integrates with Google Earth, and communicates with an OSGi-based fleet management control center using a variety of protocols. At a high level, Toast consists of a client and a back end. The Toast Client provides a variety of functionality, including an emergency application that notifies the control center of the vehicle’s GPS location when the airbag deploys, an application that tracks the vehicle’s GPS location and periodically notifies the control center, a touch screen interface to control the vehicle’s audio and climate systems, and a turn-by-turn navigation system. The Toast Back End is developed over the course of the book from a simple emergency monitoring station to an extensible fleet management platform for managing and controlling vehicles. This includes vehicle discovery, tracking, and software management or provisioning. The attractiveness of Toast as an example goes beyond the familiarity of telematics, fleet management, and the functionality of the various applications. It is compellingly extensible—we can reasonably explore a number of technologies without making it up. More important, this range of scenarios enables us to discuss a variety of real-world OSGi-related challenges: Bundle granularity—A deployed Toast system, including the in-vehicle client, the device simulators, and the control center, amounts to over 100 bundles. That may seem like a lot, but this architecture both is representative of real systems and allows us to demonstrate a number of best practices in dealing with large numbers of interdependent bundles. Third-party libraries—Most real-world applications make use of third-party code; Toast is no exception. The full Toast application uses libraries from Eclipse, Apache, the broader Java open-source community, as well as Google JavaScript. We walk you through the process of incorporating non-OSGiaware third-party libraries into an OSGi-based application and detail the issues to watch for. Dynamic installation and removal of functionality—Toast is a highly dynamic application with functionality being installed and removed, servers and clients interacting, and user input being handled. Through this example we show how to write bundles that adapt to the appearance of new functionality and to the removal or updating of dependencies. We also show how to use p2, the latest deployment implementation in Equinox, to manage executable Equinox profiles.
McAffer_OSGi.book Page 33 Monday, January 11, 2010 12:21 PM
3.1 What Is Toast?
33
Extensibility and collaboration—As you walk through the development of Toast, you’ll see a number of approaches to extensibility and collaboration, including services, Declarative Services, the Whiteboard Pattern, the Equinox Extension Registry, and more. Writing new functionality is relatively straightforward, and Toast combines this with support for the dynamic installation and removal of applications to create a powerful software platform. Testing and simulation strategies—Throughout the book, Toast develops into a reasonably complex application. Accordingly, we provide examples and best practices for automated testing, from POJO (plain old Java object) development to using mock objects and JUnit in OSGi-based system tests. We also show how to test against simulated devices for situations where real device hardware is either unavailable or cannot be used economically. Deploying real airbags can get very expensive! Off-board communications—Very few systems today stand alone. Most eventually communicate to off-board servers, peers, or subsystems. For example, Toast clients use HTTP to communicate with the control center running the Jetty web server. The device simulator uses a similar approach but embeds a small web server in the vehicle itself. Provisioning is done using the Eclipse Communications Framework (ECF) to talk to software repositories. Graphical and web-based user interfaces—Using OSGi certainly does not require a user interface; many real-world applications are “headless.” Nevertheless, Toast provides a number of UI examples—the graphical user interface intended for an in-vehicle touch screen, a simple web UI for the control center, and JavaScript web-based UI for the device simulator. Finally, Toast is fun. It is simple and easily understood, yet rich enough to provide a basis for a variety of applications and technology integrations. The lessons learned in developing Toast are readily applicable to other domains and applications.
TOAST IS ALSO AT ECLIPSE Putting together Toast has been very informative and gratifying. As we show it to other people at Eclipse, they have lots of great ideas for how to extend and improve it. To facilitate this we have donated a snapshot of Toast, the code as of Chapter 14, to the Examples project at Eclipse. We fully hope and expect that it will evolve beyond the example you see here. See http://wiki.eclipse.org/Toast for more information.
McAffer_OSGi.book Page 34 Monday, January 11, 2010 12:21 PM
34
CHAPTER 3 • Tutorial Introduction
3.2 The Evolution of Toast In Part II we develop Toast over a number of tutorial chapters. Each chapter presents a new concept and makes incremental progress over the preceding chapter. As development progresses, we supply the code for each iteration. Where it helps to clarify the discussion, the chapter text includes code snippets. The Samples Manager, available from http://equinoxosgi.org, contains the complete code for each chapter and allows you to perform each step on your own, browse the code, or simply read the text. Hello, Toast (Chapter 4)—The tutorial starts with an empty workspace and walks you through creating a simple emergency application—whenever the airbag deploys, the GPS location is read and displayed along with an emergency message. This simple example is then split into three bundles and the reasoning and tooling explained. By the end of this section, you will have a running application based on a collection of OSGi bundles. Services (Chapter 5)—This chapter moves beyond the notion of bundles and presents the concept of services and inter-bundle collaboration. More than just code libraries, bundles can register services with the OSGi service registry, allowing other bundles to make use of their provided functionality without being tightly coupled. Dynamic Services (Chapter 6)—Here we introduce the idea that bundles and services can come and go throughout the lifetime of a running system. This dynamic behavior is one of the hallmarks of OSGi-based systems and figures heavily in many of the design and implementation decisions you make. In this discussion we first present OSGi Service Trackers, followed by a review of a third-party toolkit for service management called the Service Activator Toolkit (SAT). Finally, we present OSGi’s Declarative Services (DS). DS is adopted for the remainder of the tutorial. At the end of this section, Toast embraces the dynamic nature of the scenario. Client/Server Interaction (Chapter 7)—At this point Toast operates as a stand-alone OSGi application. To fully implement the emergency scenario, we need a control center to receive emergency notifications. In this chapter we implement the control center as a separate OSGi system. We use HTTP for the communication between the client system and the control center. By chapter’s end, Toast runs on separate machines, and the emergency application sends emergency information to the control center. Testing (Chapter 8)—Even though there is little complex domain logic in Toast at this point, we can begin to write tests for what we do have. This
McAffer_OSGi.book Page 35 Monday, January 11, 2010 12:21 PM
3.2 The Evolution of Toast
35
chapter uses EasyMock and JUnit to test the domain logic in the emergency scenario. This demonstrates that when domain logic is clearly separated from the OSGi plumbing, the barrier to creating a suite of automated tests is significantly lowered. We also show how to write OSGi-based system tests. Packaging (Chapter 9)—It’s time to snip the strings and export Toast so it runs outside the workspace and can be distributed to others. In this chapter you will learn how to build, publish, and zip signed and otherwise exported bundles from your workspace. We also show you how to combine them with prebuilt bundles from others and ultimately distribute your system. Pluggable Services (Chapter 10)—At this point in Toast, we have only mocked up the devices in our application. In this chapter we add a device simulator that presents a simple web user interface, allowing you to virtually operate the vehicle devices (e.g., GPS and airbag) and thus interact with Toast subsystems. This chapter demonstrates how to structure your system to allow for the pluggability of alternate implementations of the same service. Extensible User Interface (Chapter 11)—From an end-user perspective Toast is pretty boring so far. Here we add a Standard Widget Toolkit (SWT)-based graphical user interface that allows users to operate the installed Toast Client subsystems. We also add support for audio and climate control and integrate Google Earth for navigation mapping and guidance support. These additions demonstrate a number of interesting OSGi topics, and we are careful to point these out. Dynamic Configuration (Chapter 12)—Toast is designed to be highly configurable. In this chapter you will learn how to manage this configurability using the OSGi Configuration Admin service by way of an example. We add to Toast a new application that tracks the vehicle’s location and periodically reports to the control center. It uses OSGi’s Configuration Admin service to determine how often the tracking application reports to the control center. Web Portal (Chapter 13)—In this chapter we add a web UI to the control center. While the web UI itself is quite simple, we create an extensible portal to demonstrate how to effectively use Declarative Services and the Whiteboard Pattern. System Deployment with p2 (Chapter 14)—A key aspect of OSGi-based systems is their flexibility in deployment. Telematics systems in particular benefit from the ability to remotely manage the software and information. This chapter introduces and demonstrates the use of the Equinox p2 provisioning platform to install, configure, update, and remove functionality from a Toast system.
McAffer_OSGi.book Page 36 Monday, January 11, 2010 12:21 PM
36
CHAPTER 3 • Tutorial Introduction
3.3 Development Environment Installation The code in this book has been developed and tested using the Galileo SR1 release of Eclipse. You can get this from http://eclipse.org/downloads. Since Eclipse is a tooling platform, several different configurations are available. The Eclipse team has put together a tooling configuration specifically for people building bundlebased applications—people like you—the “Eclipse for RCP/Plug-in Developers” package. Despite the name, this is a comprehensive set of tools for writing OSGi bundles. More generally, any of the packages that include the Java Development Tools (JDT) or Plug-in Development Environment (PDE) will work fine. Each package is available for a variety of machine configurations and is mirrored across the globe. Choose the appropriate package, platform, and mirror and download the tooling. Having downloaded Eclipse, extract the file to a location of your choosing. In the examples we assume you have extracted it to c:\ide.
OBTAINING A JAVA RUNTIME ENVIRONMENT The Eclipse downloads do not include a Java Runtime Environment (JRE) or Java Software Development Kit (SDK). Many systems include an acceptable JRE. If yours does not, http://eclipse.org/downloads/moreinfo/jre.php provides information on getting a suitable JRE. For the work in this book, it is convenient to have a Java SDK (sometimes called a JDK), as it includes much of the Java source, convenient for debugging, and a couple of handy tools such as jarsigner.
To start Eclipse, run the Eclipse application launcher (eclipse.exe or eclipse, depending on your operating system). As Eclipse starts, you’ll be prompted for a workspace location. The workspace is the place your development artifacts are stored. It is typically a good idea to locate the workspace somewhere separate from the Eclipse installation. This simplifies the management of multiple workspaces as well as changing versions of Eclipse. By default a location in your user directory (e.g., c:\Documents and Settings\Administrator\workspace) will be suggested. This is a fine choice if you are just starting out.
3.4 Sample Code As mentioned earlier, Toast ultimately ends up including many bundles. Many of these evolve quite a bit as the tutorial and deep-dive chapters progress. It turns
McAffer_OSGi.book Page 37 Monday, January 11, 2010 12:21 PM
3.4 Sample Code
37
out that managing a dozen different versions of the same bundles in coherent sets is quite complicated. To help with this, we supply a Samples Manager tool to help you both manage the sample code and move from sample to sample. The Samples Manager includes all of the code and resources for each sample. To install the Samples Manager, run your Eclipse IDE and follow these steps: ❍
Open the Software Updates dialog using Help > Install New Software….
❍
On the Available Software page click the Add… button to add a new software site.
❍
In the dialog, enter http://equinoxosgi.org, the location of the book’s software site, and click OK.
❍
Expand the tree for the Equinox OSGi Book site and select the Samples Manager. Pick the most recent version available. (You may need to uncheck the Group Items by Category option to see it.)
❍
Click Next and go through the following pages of the wizard, carefully reading and accepting the licenses and warnings. After the Samples Manager is installed, a restart dialog appears. Select Restart.
3.4.1 Moving from Chapter to Chapter When the Samples Manager is installed, an OSGi/Equinox menu appears on the main menu bar. Run the tool by selecting OSGi/Equinox > Samples Manager, and the Samples Manager view as shown in Figure 3-1 will appear.
Figure 3–1 Samples Manager
The list shows all chapters of the book that have associated sample code. Note that some chapters have multiple samples. Select a sample and click Import
McAffer_OSGi.book Page 38 Monday, January 11, 2010 12:21 PM
38
CHAPTER 3 • Tutorial Introduction
on the toolbar or in the context menu to load all projects related to the selected sample into the workspace. One goal of the Samples Manager is to ensure that you can move from sample to sample smoothly and with a clean slate. If you have a problem, you can simply reload. As such, the Samples Manager deletes all sample projects that it previously loaded before loading the new projects. It also cleans up the launch configurations related to the samples. This ensures that your workspace contains only the projects for one sample and is in a known state.
YOUR CONTENT MAY BE DISCARDED When moving to a new sample, the Samples Manager deletes old sample projects even if you have changed them. So your intermediate results may be discarded.
The Samples Manager view highlights the chapter that you have loaded to remind you what is in the workspace. The Samples Manager’s Help content includes the most recent instructions and tips for its use.
3.4.2 Comparing The Samples Manager also supports comparing the workspace to the set of projects for a sample. This is extremely useful when you are following the tutorial steps. For example, while doing Chapter 7, you may find that something is not working or the steps are unclear. Comparing the workspace to the Chapter 7 sample tells you what is left to do or where your setup is different from the expected outcome. Similarly, you can selectively load parts of a solution into the workspace. Several chapters require sets of resources or large chunks of code that you are not expected to create on your own. Use the Samples Manager’s comparison tool, as shown in Figure 3-2, and the Copy into Workspace action to select files and folders and copy them into the workspace. Notice that the compare editor distinguishes between files that have changes and those that exist in only one location. Changes are highlighted in the lower area, and there is a minus sign (-) if a file does not exist in the workspace but does exist in the comparison chapter. Conversely, a plus sign (+) is shown if a file exists only in the workspace. So in this example, the Bundle-Activator header was removed when moving to Sample 6.4 and the Service-Component header was added.
McAffer_OSGi.book Page 39 Monday, January 11, 2010 12:21 PM
3.5 Target Platform Setup
39
Figure 3â&#x20AC;&#x201C;2 Comparing Sample 5 to Sample 6.4
THINK THIS IS BACKWARDS? Some readers may think that this is backwards. Think of the compare editor as telling you what the workspace has or does not have compared to the other sample you selected.
3.5 Target Platform Setup Before starting to write code, you need to set up the workspaceâ&#x20AC;&#x2122;s target platform. The target platform is the set of bundles against which your code is compiled and from which you will create your systems. The target is distinct from the bundles that happen to make up your Eclipse tooling. In our case, the target includes mostly just the Equinox bundles needed to implement Toast. We can add and remove bundles in the target without affecting the tools.
McAffer_OSGi.book Page 40 Monday, January 11, 2010 12:21 PM
40
CHAPTER 3 • Tutorial Introduction
TARGETING THE IDE By default PDE uses your Eclipse IDE bundles as the target platform. This is convenient and sufficient for the initial stages of Toast but will quickly become unwieldy. As such, we recommend using a distinct target platform.
PDE includes comprehensive support for defining and managing target platforms. The Samples Manager you installed also includes a predefined target suitable for the examples in this book. To get up and running quickly, load the target using the Samples Manager. You can also start from scratch and assemble your own target definition. We describe both workflows in the following sections. Eventually Toast will have client and server parts. In the examples you will add graphical UIs and do some testing. The target must have all the supporting bundles needed for these activities. Broadly speaking, you need four things: Equinox SDK—The Equinox team has put together an all-in-one SDK that includes all the Equinox bundles, along with their source and a few supporting bundles such as Jetty and some Apache pieces. This is the core of what Toast needs. RCP SDK—Later on in Toast we will add a graphical UI. Although the UI will not technically be an Eclipse RCP application, it will use the Eclipse SWT. We will also use a few utility bundles from the base Eclipse platform. It is easiest just to add the whole RCP SDK to the target. Delta pack—Equinox supports many different hardware and OS platforms. The binary executable and graphical libraries are platform specific. To ease consumption, the Eclipse team has put together a delta pack that contains all of the parts of the basic Eclipse infrastructure that are platform specific. This is of use to Toast when exporting and building. Testing stuff—In Chapter 8, “Testing,” we use libraries such as JUnit and EasyMock. These need to be in the target as well.
3.5.1 The Predefined Target The Samples Manager comes with a handy target that includes all of the components just listed. Carry out the following steps to load the predefined target: ❍
Use the Load Target entry in the Samples Manager’s toolbar menu as shown in Figure 3-1 to initiate the target load. This should show a progress dialog while the target contents are copied into the workspace. When it completes,
McAffer_OSGi.book Page 41 Monday, January 11, 2010 12:21 PM
3.5 Target Platform Setup
41
there will be a project called ToastTarget that contains the target definition and some of the target content. ❍
Open the Target Platform preferences page, Window > Preferences > Plug-in Development > Target Platform, and look for the entry called Toast Target. Select the check box beside that entry and click Apply or OK to use the Toast target.
DO NOT SET THE TARGET THROUGH THE TARGET EDITOR Targets are defined in .target files. The Toast target is in toast.target in the ToastTarget project you just loaded. We will talk about editing this file in the next section, but here you should be aware of a bug in PDE as of Galileo SR1 (September 2009). When a target file is opened, the target editor attempts to resolve its contents. To do this, it attempts to open all of the bundles in the target. The Toast target refers to some remote software repositories at eclipse.org for some of its bundles. The net result is that when the target file is opened, PDE starts downloading the constituents of the target from some remote server. This can be many megabytes. In many cases this is a bonus—pre-caching the content. However, there is a bug. Do not click on the Set as target platform link in the target editor while the editor is resolving. Doing this causes a race condition that ultimately prevents the target definition from loading. If you do this by mistake, you will see a dialog reporting a locking or synchronization error. When this happens, close the target editor and restart Eclipse. Then follow the steps for loading the target using the target preferences, or open the editor and wait for it to complete resolving before clicking the link.
Targets in the Workspace The workflow described in this section results in at least part of the Toast target living in a project in your workspace. In a sense this is strange—the target is all the stuff not in your workspace?! It turns out to be very convenient to treat the content of the target as a resource that you can put into source control and share. In the development of the book samples and our work with product teams it is quite common to have a project in the workspace and Software Configuration Management (SCM) system to contain the content and target definition. New team members can simply check out the target. When one team member changes the target, the others need only update. This is complementary to the target platform Software Site facilities described in the following section.
McAffer_OSGi.book Page 42 Monday, January 11, 2010 12:21 PM
42
CHAPTER 3 • Tutorial Introduction
3.5.2 Defining Target Platforms We recommend that you use the predefined target from the previous section, since it will make things easier later on when we add to the target. But at some point you will have to define your own target for your own projects. This section describes how that is done. If you are up and running with the predefined target, skip this section and treat it as a reference to come back to. A target platform is just a list of bundles and features from various locations. Sources of content include the following: Directories—Specifying a directory adds to the target all bundles and features found in that directory. Directories of bundles can be acquired by downloading archives from the web, for example, from eclipse.org. Installations—Pointing the target at an existing Eclipse install adds all elements of the install to the target platform. This includes linked folders, drop-ins, and any other bundles and features that make up the install. Features—Adding features is similar to adding a directory but with the added ability to select a subset of features found in the directory. All bundles indicated by the selected features are also added to the target. Software sites—There are many software repositories around the world. Adding a software site allows you to identify a repository from which bundles and features are loaded. The predefined Toast target uses directories and software sites in its definition. We will next walk you through the steps we used to create the target platform. In a sidebar in the previous section we talked about having targets in the workspace. We illustrate that approach here. If you would rather not, you can put the target content wherever you like, but the target definition file still has to go somewhere in the workspace. ❍
Create a simple project using File > New > Project… > General > Project. We have called our target project ToastTarget.
❍
Create a target platform definition using the File > New > Other > Plug-in Development > Target Definition wizard.
❍
Enter a name for the target file and situate it in the new project.
❍
Notice at the bottom of the wizard that there are several options for initializing your new target. Select the Nothing option, as we are building this target from scratch. In other scenarios you may wish to prime your new target with content listed elsewhere.
❍
Click Finish to complete the wizard and open the target editor on the new target definition, as shown in Figure 3-3.
McAffer_OSGi.book Page 43 Monday, January 11, 2010 12:21 PM
3.5 Target Platform Setup
43
Figure 3–3 Target definition editor
In the editor you can fill in a useful name for the target definition, but the really interesting part is the Locations section. For the Toast target we will need to add a directory for the delta pack and a software site for the Equinox and RCP SDKs. Let’s do the delta pack first: ❍
Get the delta pack by downloading it from the Eclipse project download site, http://download.eclipse.org/eclipse. Choose Latest Release from the options given. As of this writing it is 3.5.1.
❍
On the subsequent download page, select Delta Pack on the left navigation bar. This scrolls the page to the delta pack download link. Notice there is only one link since, by definition, it is all the pieces that are platform dependent. Select the link and save the archive to a convenient spot on your local drive.
❍
When the download is complete, create a new folder called delta.pack in the target project and import the downloaded content into the folder using File > Import… > General > Archive File.
McAffer_OSGi.book Page 44 Monday, January 11, 2010 12:21 PM
44
CHAPTER 3 • Tutorial Introduction
❍
In the target editor’s Definition page, click Add… and select Directory. Click Next.
❍
In the Add Content dialog, click Variables… and select workspace_loc from the list. Now append the workspace path to the delta pack content. For example, the location should look like the following, assuming you called your project ToastTarget: ${workspace_loc}/ToastTarget/delta.pack/ eclipse
Next we’ll add a software site and get the Equinox and RCP SDKs: ❍
In the target editor’s Definition page, click Add… and select Software Site. Click Next.
❍
On the subsequent Add Software Site wizard page, choose Galileo in the Work with drop-down. If there is a more recent release repository available, feel free to choose it. If the site you want is not listed, click the Add… button and enter the Name and URL Location for the site. For example, the Galileo site is at http://download.eclipse.org/releases/galileo.
❍
Once the site is selected, the content area of the wizard should fill in as shown in Figure 3-4. Select the Equinox Project SDK either by expanding the EclipseRT Target Platform Components category or by typing its name in the filter area above the content.
❍
To add the RCP SDK, you have to uncheck the Group by Category box under the content area and then type RCP in the filter area. Select Eclipse RCP SDK.
MULTI-SELECTION MAY NOT WORK There is a bug in some versions of Eclipse where using the filter can clear the current selection. You may need to add the SDKs individually or not use the filter.
❍
Important! You must uncheck the “Include required software” box at the bottom left of the wizard. Failure to do this will result in a bloated target that may not work for the Toast scenario.
After you click Finish, PDE resolves the target. This may take some time as the content is downloaded from the software site. Once the resolution is com-
McAffer_OSGi.book Page 45 Monday, January 11, 2010 12:21 PM
3.5 Target Platform Setup
45
Figure 3â&#x20AC;&#x201C;4 Galileo software site content
plete, take a look at the Content page of the target editor. You should see something similar to the editor shown in Figure 3-5. Notice that we have left the addition of the testing-related bundles as a further exercise for the reader. Basically you add a directory to the target definition and target and then collect the bundles and features you want. Once the target definition is completed, save the file and click Set as Target Platform at the top right.
McAffer_OSGi.book Page 46 Monday, January 11, 2010 12:21 PM
46
CHAPTER 3 • Tutorial Introduction
Figure 3–5 Toast target content
3.6 Learning by Example One of the most efficient and effective ways of figuring out how to program a system is by browsing examples. We can’t emphasize this enough. It can be overwhelming, but there are various shortcuts and mechanisms you can use to help follow the code. Here is a short list of the workspace navigation operations we use on a day-to-day basis: Navigate > Open Type… (Ctrl+Shift+T)—Opens the Java type with the name you enter. Wildcards and case-sensitive acronyms, CamelCasing, are supported. Use this to discover where a type is or if it exists. Navigate > Open Resource… (Ctrl+Shift+R)—Opens a resource with the name you enter. Wildcards and case-sensitive acronyms, CamelCasing, are supported. Use this to discover resources and their location.
McAffer_OSGi.book Page 47 Monday, January 11, 2010 12:21 PM
3.6 Learning by Example
47
Open Plug-in Artifact (Ctrl+Shift+A)—Opens the artifact that defines the plug-in or bundle element that you enter. For example, enter the ID of an extension and the plugin.xml defining the extension is opened. Ctrl-3—Presents a condensed list of commands, views, preferences, and other facilities. Simply type some words related to what you need and the list is filtered. For example, typing targ finds the target preference page. Navigate > Quick Type Hierarchy (Ctrl+T)—Pops up a type hierarchy rooted by the type associated with the selection in the current Java editor. For example, if the selection is on a type, a normal type hierarchy is opened. If the selection is on or in a method, all implementers of that method in the hierarchy are shown. Press Ctrl+T again to invert the hierarchy. Search > References > Workspace (Ctrl+Shift+G)—Searches for references to the selected Java element (e.g., type, method, field) in the Java editor. Ctrl+Shift+U does the same search but local to the file. Navigate > Open Declaration (F3)—Opens the declaration of the Java element selected in the current Java editor. OSGi systems are quite decoupled. That’s the whole point! This can, however, make it hard to figure out how various pieces interact. PDE offers various tools and mechanisms for navigating these interconnections. The Plug-in Development perspective (Window > Open Perspective > Other… > Plug-in Development) includes a Plug-ins view. From this you can easily navigate the dependencies and references. From the PDE plug-in editor you can discover the extension-to-extension-point interconnections, navigate to the classes defined in various extensions, and browse extension point documentation.
ADDING TYPES TO JAVA SEARCH In the Plug-ins view, select all plug-ins and use the Add to Java Search context menu entry or the view toolbar button to add all known plug-ins to the Java search scope. This is helpful because Java search looks only in projects in the workspace and their dependent projects. This means that if you open an empty workspace and try to open a type, say, using Ctrl+Shift+T, the type selection dialog will be empty. By adding all plug-ins in your target platform to the search, you can more easily navigate example code and classes that you do not reference from your projects.
McAffer_OSGi.book Page 48 Monday, January 11, 2010 12:21 PM
48
CHAPTER 3 â&#x20AC;˘ Tutorial Introduction
3.7 Summary Once set up, Eclipse and PDE make it easy to create and run OSGi systems. The setup detailed here is robust in that you can use the IDE to work on many different workspaces, the target platform can be updated independently of the IDE or the workspaces, and the IDE itself can be updated without affecting the workspaces or the target platform. By adding Samples Manager support, you can jump to any chapter and set up your workspace in seconds. You can also validate the tutorial steps as you go and get quick summaries of all the changes done so far and those left to do.
CHAPTER 2
Test-Driven Development with Objects
BUY
from informIT and
SAVE 35%
Enter the coupon code JAVAONE10 during checkout.
Google Bookmarks
Delicious
Digg
StumbleUpon
Growing Object-Oriented Software, Guided by Tests AUTHORS Steve Freeman and Nat Pryce
FORMATS & ISBNs Book: 9780321503626 eBook: 9780321699756 Safari Books Online: 9780321574442
“ The authors of this book have led a revolution in the craft of programming by controlling the environment in which software grows.” — WARD CUNNINGHAM
“ At last, a book suffused with code that exposes the deep symbiosis between TDD and OOD. This one’s a keeper.” —ROBERT C. MARTIN
Test-Driven Development (TDD) is now an established technique for delivering better software faster. TDD is based on a simple idea: Write tests for your code before you write the code itself. However, this “simple” idea takes skill and judgment to do well. Now there’s a practical guide to TDD that takes you beyond the basic concepts. Drawing on a decade of experience building real-world systems, two TDD pioneers show how to let tests guide your development and “grow” software that is coherent, reliable, and maintainable.
Steve Freeman and Nat Pryce describe the processes they use, the design principles they strive to achieve, and some of the tools that help them get the job done. Through an extended worked example, you’ll learn how TDD works at multiple levels, using tests to drive the features and the object-oriented structure of the code, and using Mock Objects to discover and then describe relationships between objects. Along the way, the book systematically addresses challenges that development teams encounter with TDD — from integrating TDD into your processes to testing your most difficult features.
COVERAGE INCLUDES • Implementing TDD effectively • Creating cleaner, more expressive, more sustainable code • Using tests to stay relentlessly focused on sustaining quality • Understanding how TDD, Mock Objects, and Object-Oriented Design come together in the context of a real software development project • Using Mock Objects to guide object-oriented designs
informit.com/learnjava
Chapter 2
Test-Driven Development with Objects Music is the space between the notes. —Claude Debussy
A Web of Objects Object-oriented design focuses more on the communication between objects than on the objects themselves. As Alan Kay [Kay98] wrote: The big idea is “messaging” […] The key in making great and growable systems is much more to design how its modules communicate rather than what their internal properties and behaviors should be.
An object communicates by messages: It receives messages from other objects and reacts by sending messages to other objects as well as, perhaps, returning a value or exception to the original sender. An object has a method of handling every type of message that it understands and, in most cases, encapsulates some internal state that it uses to coordinate its communication with other objects. An object-oriented system is a web of collaborating objects. A system is built by creating objects and plugging them together so that they can send messages to one another. The behavior of the system is an emergent property of the composition of the objects—the choice of objects and how they are connected (Figure 2.1). This lets us change the behavior of the system by changing the composition of its objects—adding and removing instances, plugging different combinations together—rather than writing procedural code. The code we write to manage this composition is a declarative definition of the how the web of objects will behave. It’s easier to change the system’s behavior because we can focus on what we want it to do, not how.
Values and Objects When designing a system, it’s important to distinguish between values that model unchanging quantities or measurements, and objects that have an identity, might change state over time, and model computational processes. In the
13
14
Chapter 2
Test-Driven Development with Objects
Figure 2.1
A web of objects
object-oriented languages that most of us use, the confusion is that both concepts are implemented by the same language construct: classes. Values are immutable instances that model fixed quantities. They have no individual identity, so two value instances are effectively the same if they have the same state. This means that it makes no sense to compare the identity of two values; doing so can cause some subtle bugs—think of the different ways of comparing two copies of new Integer(999). That’s why we’re taught to use string1.equals(string2) in Java rather than string1 == string2. Objects, on the other hand, use mutable state to model their behavior over time. Two objects of the same type have separate identities even if they have exactly the same state now, because their states can diverge if they receive different messages in the future. In practice, this means that we split our system into two “worlds”: values, which are treated functionally, and objects, which implement the stateful behavior of the system. In Part III, you’ll see how our coding style varies depending on which world we’re working in. In this book, we will use the term object to refer only to instances with identity, state, and processing—not values. There doesn’t appear to be another accepted term that isn’t overloaded with other meanings (such as entity and process).
Follow the Messages We can benefit from this high-level, declarative approach only if our objects are designed to be easily pluggable. In practice, this means that they follow common communication patterns and that the dependencies between them are made explicit. A communication pattern is a set of rules that govern how a group of objects talk to each other: the roles they play, what messages they can send and when, and so on. In languages like Java, we identify object roles with (abstract) interfaces, rather than (concrete) classes—although interfaces don’t define everything we need to say.
Follow the Messages In our view, the domain model is in these communication patterns, because they are what gives meaning to the universe of possible relationships between the objects. Thinking of a system in terms of its dynamic, communication structure is a significant mental shift from the static classification that most of us learn when being introduced to objects. The domain model isn’t even obviously visible because the communication patterns are not explicitly represented in the programming languages we get to work with. We hope to show, in this book, how tests and mock objects help us see the communication between our objects more clearly. Here’s a small example of how focusing on the communication between objects guides design. In a video game, the objects in play might include: actors, such as the player and the enemies; scenery, which the player flies over; obstacles, which the player can crash into; and effects, such as explosions and smoke. There are also scripts spawning objects behind the scenes as the game progresses. This is a good classification of the game objects from the players’ point of view because it supports the decisions they need to make when playing the game—when interacting with the game from outside. This is not, however, a useful classification for the implementers of the game. The game engine has to display objects that are visible, tell objects that are animated about the passing of time, detect collisions between objects that are physical, and delegate decisions about what to do when physical objects collide to collision resolvers.
Figure 2.2
Roles and objects in a video game
As you can see in Figure 2.2, the two views, one from the game engine and one from the implementation of the in-play objects, are not the same. An Obstacle, for example, is Visible and Physical, while a Script is a Collision Resolver and Animated but not Visible. The objects in the game play different roles depending
15
16
Chapter 2
Test-Driven Development with Objects
on what the engine needs from them at the time. This mismatch between static classification and dynamic communication means that we’re unlikely to come up with a tidy class hierarchy for the game objects that will also suit the needs of the engine. At best, a class hierarchy represents one dimension of an application, providing a mechanism for sharing implementation details between objects; for example, we might have a base class to implement the common features of frame-based animation. At worst, we’ve seen too many codebases (including our own) that suffer complexity and duplication from using one mechanism to represent multiple concepts.
Roles, Responsibilities, Collaborators We try to think about objects in terms of roles, responsibilities, and collaborators, as best described by Wirfs-Brock and McKean in [Wirfs-Brock03]. An object is an implementation of one or more roles; a role is a set of related responsibilities; and a responsibility is an obligation to perform a task or know information. A collaboration is an interaction of objects or roles (or both). Sometimes we step away from the keyboard and use an informal design technique that Wirfs-Brock and McKean describe, called CRC cards (Candidates, Responsibilities, Collaborators). The idea is to use low-tech index cards to explore the potential object structure of an application, or a part of it. These index cards allow us to experiment with structure without getting stuck in detail or becoming too attached to an early solution.
Figure 2.3
CRC card for a video game
But Sometimes Ask
Tell, Don’t Ask We have objects sending each other messages, so what do they say? Our experience is that the calling object should describe what it wants in terms of the role that its neighbor plays, and let the called object decide how to make that happen. This is commonly known as the “Tell, Don’t Ask” style or, more formally, the Law of Demeter. Objects make their decisions based only on the information they hold internally or that which came with the triggering message; they avoid navigating to other objects to make things happen. Followed consistently, this style produces more flexible code because it’s easy to swap objects that play the same role. The caller sees nothing of their internal structure or the structure of the rest of the system behind the role interface. When we don’t follow the style, we can end up with what’s known as “train wreck” code, where a series of getters is chained together like the carriages in a train. Here’s one case we found on the Internet: ((EditSaveCustomizer) master.getModelisable() .getDockablePanel() .getCustomizer()) .getSaveItem().setEnabled(Boolean.FALSE.booleanValue());
After some head scratching, we realized what this fragment was meant to say: master.allowSavingOfCustomisations();
This wraps all that implementation detail up behind a single call. The client of master no longer needs to know anything about the types in the chain. We’ve reduced the risk that a design change might cause ripples in remote parts of the codebase. As well as hiding information, there’s a more subtle benefit from “Tell, Don’t Ask.” It forces us to make explicit and so name the interactions between objects, rather than leaving them implicit in the chain of getters. The shorter version above is much clearer about what it’s for, not just how it happens to be implemented.
But Sometimes Ask Of course we don’t “tell” everything;1 we “ask” when getting information from values and collections, or when using a factory to create new objects. Occasionally, we also ask objects about their state when searching or filtering, but we still want to maintain expressiveness and avoid “train wrecks.” For example (to continue with the metaphor), if we naively wanted to spread reserved seats out across the whole of a train, we might start with something like:
1. Although that’s an interesting exercise to try, to stretch your technique.
17
18
Chapter 2
Test-Driven Development with Objects
public class Train { private final List<Carriage> carriages […] private int percentReservedBarrier = 70; public void reserveSeats(ReservationRequest request) { for (Carriage carriage : carriages) { if (carriage.getSeats().getPercentReserved() < percentReservedBarrier) { request.reserveSeatsIn(carriage); return; } } request.cannotFindSeats(); } }
We shouldn’t expose the internal structure of Carriage to implement this, not least because there may be different types of carriages within a train. Instead, we should ask the question we really want answered, instead of asking for the information to help us figure out the answer ourselves: public void reserveSeats(ReservationRequest request) { for (Carriage carriage : carriages) { if (carriage.hasSeatsAvailableWithin(percentReservedBarrier)) { request.reserveSeatsIn(carriage); return; } } request.cannotFindSeats(); }
Adding a query method moves the behavior to the most appropriate object, gives it an explanatory name, and makes it easier to test. We try to be sparing with queries on objects (as opposed to values) because they can allow information to “leak” out of the object, making the system a little bit more rigid. At a minimum, we make a point of writing queries that describe the intention of the calling object, not just the implementation.
Unit-Testing the Collaborating Objects We appear to have painted ourselves into a corner. We’re insisting on focused objects that send commands to each other and don’t expose any way to query their state, so it looks like we have nothing available to assert in a unit test. For example, in Figure 2.4, the circled object will send messages to one or more of its three neighbors when invoked. How can we test that it does so correctly without exposing any of its internal state? One option is to replace the target object’s neighbors in a test with substitutes, or mock objects, as in Figure 2.5. We can specify how we expect the target object to communicate with its mock neighbors for a triggering event; we call these specifications expectations. During the test, the mock objects assert that they
Support for TDD with Mock Objects
Figure 2.4
Figure 2.5
Unit-testing an object in isolation
Testing an object with mock objects
have been called as expected; they also implement any stubbed behavior needed to make the rest of the test work. With this infrastructure in place, we can change the way we approach TDD. Figure 2.5 implies that we’re just trying to test the target object and that we already know what its neighbors look like. In practice, however, those collaborators don’t need to exist when we’re writing a unit test. We can use the test to help us tease out the supporting roles our object needs, defined as Java interfaces, and fill in real implementations as we develop the rest of the system. We call this interface discovery; you’ll see an example when we extract an AuctionEventListener in Chapter 12.
Support for TDD with Mock Objects To support this style of test-driven programming, we need to create mock instances of the neighboring objects, define expectations on how they’re called and then check them, and implement any stub behavior we need to get through the test. In practice, the runtime structure of a test with mock objects usually looks like Figure 2.6.
19
20
Chapter 2
Test-Driven Development with Objects
Figure 2.6
Testing an object with mock objects
We use the term mockery2 for the object that holds the context of a test, creates mock objects, and manages expectations and stubbing for the test. We’ll show the practice throughout Part III, so we’ll just touch on the basics here. The essential structure of a test is: • Create any required mock objects. • Create any real objects, including the target object. • Specify how you expect the mock objects to be called by the target object. • Call the triggering method(s) on the target object. • Assert that any resulting values are valid and that all the expected calls have been made. The unit test makes explicit the relationship between the target object and its environment. It creates all the objects in the cluster and makes assertions about the interactions between the target object and its collaborators. We can code this infrastructure by hand or, these days, use one of the multiple mock object frameworks that are available in many languages. The important point, as we stress repeatedly throughout this book, is to make clear the intention of every test, distinguishing between the tested functionality, the supporting infrastructure, and the object structure.
2. This is a pun by Ivan Moore that we adopted in a fit of whimsy.
Try Safari Books Online FREE
Get online access to 8,500+ Books and Videos
FREE TRIAL—GET STARTED TODAY! informit.com/safaritrial Find trusted answers, fast Only Safari lets you search across thousands of best-selling books from the top technology publishers, including Addison-Wesley Professional, Cisco Press, O’Reilly, Prentice Hall, Que, and Sams.
Master the latest tools and techniques In addition to gaining access to an incredible inventory of technical books, Safari’s extensive collection of video tutorials lets you learn from the leading video training experts.
WAIT, THERE’S MORE! Keep your competitive edge With Rough Cuts, get access to the developing manuscript and be among the first to learn the newest technologies.
Stay current with emerging technologies Short Cuts and Quick Reference Sheets are short, concise, focused content created to get you up-to-speed quickly on new and cutting-edge technologies.
• L iveLessons allows you to keep your skills up to date with the latest technology training from trusted author experts.
LiveLessons: self-paced, personal video instruction from the world’s leading technology experts • INSTRUCTORS YOU TRUST • CUTTING EDGE TOPICS
• LiveLessons is a cost effective alternative to expensive off-site training programs.
• C USTOMIZED, SELF-PACED LEARNING • LEARN BY DOING
• L iveLessons provides the ability to learn at your own pace and avoid hours in a classroom.
PACKAGE INCLUDES: • 1 DVD featuring 3 - 8 hours of instructor-led classroom sessions divided into 15-20 minute step-by-step hands-on labs • S ample code and printed study guide
The power of the world’s leading experts at your fingertips!
To learn more about LiveLessons visit
mylivelessons.com
Get Behind the Scenes Stay Ahead of the Curve Rough Cuts is a Safari Books Online interactive publishing service that provides you with first access to pre-published manuscripts on cutting-edge technology topics — enabling you to stay on the cutting-edge and remain competitive. When you participate in the Rough Cuts program, you also own an important role in helping to develop manuscripts into best-selling books. HERE’S HOW IT WORKS: 1. Select a Rough Cuts title. 2. Get access through a Safari Library account. Or if you would like, you can order a single Rough Cuts title as well. 3. Sign up to receive Alerts by visiting the Rough Cuts catalog page. 4. Read updated versions online or in PDF at your convenience. 5. Interact with the Rough Cuts community. Post your feedback or respond to comments from other users, editors, and authors. 6. Access the final version – online access and PDF access of the printed version is available for up to 45 days.
9780132107556
9780132160285
9780137061846
9780137045891
View Titles Available and Purchase Rough Cuts at
informit.com/roughcuts
informIT.com
THE TRUSTED TECHNOLOGY LEARNING SOURCE
I nformIT is a brand of Pearson and the online presence for the world’s leading technology publishers. It’s your source for reliable and qualified content and knowledge, providing access to the leading brands, authors, and contributors from the tech community.
LearnIT at InformIT Looking for a book, eBook, or training video on a new technology? Seeking timely and relevant information and tutorials. Looking for expert opinions, advice, and tips? InformIT has a solution. • Learn about new releases and special promotions by subscribing to a wide variety of monthly newsletters. Visit informit.com /newsletters. • FREE Podcasts from experts at informit.com /podcasts. • Read the latest author articles and sample chapters at informit.com /articles. • Access thousands of books and videos in the Safari Books Online digital library. safari.informit.com. • Get Advice and tips from expert blogs at informit.com /blogs. Visit informit.com /learn to find out all the ways you can access the hottest technology content.
Are you part of the IT crowd? Connect with Pearson authors and editors via RSS feeds, Facebook, Twitter, Youtube and more! Visit informit.com /socialconnect.
informIT.com
THE TRUSTED TECHNOLOGY LEARNING SOURCE
informit.com/usergroups
PART OF A USER GROUP? Are you a member of a group that meets to discuss IT-related topics? Your group may be eligible for the Pearson User Group Program!
BENEFITS INCLUDE : • Member discount of 35% • Monthly review copies • Information materials and more!
To learn if your group qualifies visit informit.com /usergroups