Core Java Technologies Tech Tips Tips, Techniques, and Sample Code Welcome to the Core Java Technologies Tech Tips for August 17, 2004. Here you'll get tips on using core Java technologies and APIs, such as those in Java 2 Platform, Standard Edition (J2SE). This issue covers: * Secure Communications with JSSE * Customizing the JColorChooser Component These tips were developed using Java 2 SDK, Standard Edition, v 1.4.2. This issue of the Core Java Technologies Tech Tips is written by John Zukowski, president of JZ Ventures, Inc. (http://www.jzventures.com). You can view this issue of the Tech Tips on the Web at http://java.sun.com/developer/JDCTechTips/2004/tt0817.html See the Subscribe/Unsubscribe note at the end of this newsletter to subscribe to Tech Tips that focus on technologies and products in other Java platforms. For more Java technology content, visit these sites: java.sun.com - The latest Java platform releases, tutorials, and newsletters. java.net - A web forum for collaborating and building solutions together. java.com - The marketplace for Java technology, applications and services. - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - SECURE COMMUNICATION WITH JSSE The Java Secure Socket Extension (JSSE) libraries are a standard part of the J2SE version 1.4 platform. Prior versions required installation of a standard extension library, either from Sun's reference implementation or from a third party. The libraries offer support for handling secure HTTP requests and responses, also known as HTTPS or HTTP over SSL. More specifically, JSSE libraries provide the functionality necessary for authentication, encryption, and integrity. This support ensures that data sent over the network is private and not sent as clear text (that is, text that is publicly viewable and understandable). The javax.net.ssl package contains the API for communicating through the Secure Sockets Layer (SSL) and Transport Layer Security (TLS) protocols. SSL and TLS are two closely related protocols for securing network traffic. Authentication is provided through the use of X.509 certificates -- this authentication is available for both the client and server, not just the server. In addition, the JSSE libraries offer encryption through secret key exchanges and certificate public keys. Encryption ensures message integrity, preventing a person in the middle of the communication from intercepting the message, altering it, and passing it on as if it was unchanged. If you're not interested in direct socket usage and sending HTTP commands themselves, you can use the HttpsURLConnection class that was demonstrated in the February 10, 2004 Tech Tip "Using HttpURLConnection to Access Web Pages (http://java.sun.com/developer/JDCTechTips/2004/tt0210.html#2). The HttpsURLConnection class includes full support for features such as redirection, connection retries, proxy negotiations, and (in J2SE v5) cookies. If you are interested in working with sockets directly, either for HTTP/HTTPS transactions, or for custom application-level usage, you can combine the javax.net.ssl package with the factories in javax.net. This allows you to communicate over a secured connection over which HTTPS runs. If you're familiar with communicating over an HTTP Socket connection, you'll find that working with an SSL-based socket isn't much different. You simply tend to talk over a different port, 443 instead of 80, for normal web traffic. To securely communicate, you first need to acquire a socket factory. From the factory, you create a socket for the specific host and port. Instead of calling the Socket constructor, you ask the SocketFactory for a socket. The SocketFactory you use is the default SSL version of the factory, javax.net.ssl.SSLSocketFactory: SocketFactory factory = SSLSocketFactory.getDefault(); Socket socket = factory.createSocket(hostname, HTTPS_PORT); After that, the process is essentially the same as using an HTTP-based socket. You send a request to the server, and listen for a reply: // Send Request OutputStream outputStream = socket.getOutputStream(); PrintWriter out = new PrintWriter(outputStream); out.print("GET / HTTP/1.0\r\n\r\n"); out.flush(); // Get Response InputStream inputStream = socket.getInputStream(); InputStreamReader inputStreamReader = new InputStreamReader(inputStream); BufferedReader in = new BufferedReader(inputStreamReader); String line; while ((line = in.readLine()) != null) { System.out.println(line); } Here is a program, SSLClient,that puts all the pieces together: import java.io.BufferedReader; import java.io.InputStream; import java.io.InputStreamReader; import java.io.IOException; import java.io.OutputStream; import java.io.PrintWriter; import java.net.Socket; import javax.net.SocketFactory; import javax.net.ssl.SSLSocketFactory; public class SSLClient { private static final int HTTPS_PORT = 443; public static void main(String args[]) { if (args.length == 0) { System.err.println ("Please provide a hostname to connect to"); System.exit(-1); } String hostname = args[0]; try { // Get the default SSL socket factory SocketFactory factory = SSLSocketFactory.getDefault(); // Using socket factory, get SSL socket to port on host Socket socket = factory.createSocket(hostname, HTTPS_PORT); // Send request to get root // Be sure to end string with two sets of carriage // return - newline characters. OutputStream outputStream = socket.getOutputStream(); PrintWriter out = new PrintWriter(outputStream); out.print("GET / HTTP/1.0\r\n\r\n"); out.flush(); // Fetch response InputStream inputStream = socket.getInputStream(); InputStreamReader inputStreamReader = new InputStreamReader(inputStream); BufferedReader in = new BufferedReader(inputStreamReader); String line; while ((line = in.readLine()) != null) { System.out.println(line); } // Close everything out.close(); in.close(); socket.close(); } catch (IOException e) { System.err.println("Problems talking to " + hostname); e.printStackTrace(); } } } When you run the program, pass in the name of the host you want to communicate with. Consider running Tomcat locally, or connect to sun.com. Note that the port constant in SSLClient is 443. You can alter the HTTPS port constant in the program if you need to, or you can change the program so that the constant is specified on the command line. The results of communicating over the HTTPS port might be different than the results you get when communicating over the regular HTTP port. Consider saving the output and view it in your browser. You should trim off the standard header values, such as the date and time and server. Of course, you should leave in the server value if you're interested in what server a site is using. For instance, connecting to www.sun.com through the SSLClient program: java SSLClient www.sun.com reveals the use of the SunONE WebServer 6.0 in the various header fields: HTTP/1.1 200 OK Server: SunONE WebServer 6.0 Date: Tue, 03 Aug 2004 22:30:32 GMT P3p: policyref="http://www.sun.com/p3p/Sun_P3P_Policy.xml", ... Clients should examine the credentials received to make sure that they are talking to the host they think they are. Use socket.getSession().getPeerCertificates() or getPeerPrincipal(). It's non-trivial for someone to reroute the connection to a malicious host, and this is one way to check against that. You can use the HostnameVerifier interface to verify the host name. Security-conscious developers (and users) should consider downloading the unlimited Java Cryptography Extension (JCE) policy files from the J2SE 1.4.2 downloads page (http://java.sun.com/j2se/1.4.2/download.html). The version of the JCE that comes with J2SE 1.4 is strong, but limited. Provided that you live in an eligible country, the added keysize support offers a much stronger level of cryptography capabilities than JSSE alone. To learn more about the JCE, see the JCE Reference Guide (http://java.sun.com/j2se/1.4.2/docs/guide/security/jce/JCERefGuide.html). To learn more about JSSE, see the JSSE Reference Guide (http://java.sun.com/j2se/1.4.2/docs/guide/security/jsse/JSSERefGuide.html). - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - CUSTOMIZING THE JCOLORCHOOSER COMPONENT The JColorChooser component is used to prompt the user to select a color. Like the JFileChooser, the JColorChooser itself is not a popup window, but offers direct support for placing the component in a popup window. The JColorChooser consists of a series of color chooser panels that assist in selecting a color, and a preview area to see the changes resulting from the selection. In this tip, you'll learn how to customize areas in the component. Basic JColorChooser Usage A simple use of the JColorChooser is to get the starting color for a component (typically from some object on the screen), and then display a dialog to change the component's color. The dialog includes three buttons: OK, Cancel, and Reset. Clicking the OK button returns the color change. Clicking the Cancel button returns a null color. Reset returns no color, but allows the user to reset the dialog to display the original color. Here's a program, Color1, that illustrates this simple use of the JColorChooser: import java.awt.*; import java.awt.event.*; import javax.swing.*; public class Color1 { public static void main(String args[]) { Runnable runner = new Runnable() { public void run() { JFrame frame = new JFrame("JColorChooser Sample"); frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); Container contentPane = frame.getContentPane(); final JButton button = new JButton ("Change Background"); ActionListener actionListener = new ActionListener() { public void actionPerformed (ActionEvent actionEvent) { Color initialBackground = button.getBackground(); Color newBackground = JColorChooser.showDialog( null, "Change Background", initialBackground); if (newBackground != null) { button.setBackground(newBackground); } } }; button.addActionListener(actionListener); contentPane.add(button, BorderLayout.CENTER); frame.setSize(300, 100); frame.setVisible(true); } }; EventQueue.invokeLater(runner); } } Using JColorChooser in Your Own Window Although the Color1 example illustrates the way you will likely use the JColorCooser most of the time, it certainly isn't the only way to use it. The code in Color1 calls the showDialog method of JColorChooser to place the component in a newly created, modal JDialog box. Instead, you can simply add the component to your own window -- the control is itself just a JComponent. Here's an example, Color2, that adds the JColorChooser to a window: import java.awt.*; import java.awt.event.*; import javax.swing.*; public class Color2 { public static void main(String args[]) { Runnable runner = new Runnable() { public void run() { JFrame frame = new JFrame("Second Sample"); frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); Container contentPane = frame.getContentPane(); final JLabel label = new JLabel("Hello, World", JLabel.CENTER); label.setFont( new Font("Serif", Font.BOLD | Font.ITALIC, 48)); contentPane.add(label, BorderLayout.SOUTH); Color initialBackground = label.getBackground(); final JColorChooser colorChooser = new JColorChooser(initialBackground); contentPane.add(colorChooser, BorderLayout.CENTER); // Missing source here... frame.pack(); frame.setVisible(true); } }; EventQueue.invokeLater(runner); } } Although adding the component to the window works, it doesn't display an OK or Cancel button. So you don't know when the user is done selecting a color. The Reset button isn't displayed either, but that never notified the caller of selection. If you use the JColorChooser in this second way, it is the caller's responsibility to register for interest when the color selection changes. You do this by adding a listener to the ColorSelectionModel of the chooser. By adding a listener to the model, you'll be notified of selection changes. Notice the "Missing source here..." comment in the Color2 program. If you replace the comments with the following lines, you can change the background color of the label at the bottom of the display to the newly selected color: ColorSelectionModel model = colorChooser.getSelectionModel(); ChangeListener changeListener = new ChangeListener() { public void stateChanged(ChangeEvent changeEvent) { Color newForeground = colorChooser.getColor(); label.setForeground(newForeground); } }; model.addChangeListener(changeListener); Add the appropriate imports (javax.swing.colorchooser.*; and javax.swing.event.*), and your program is fully functional again, that is, the foreground of the label changes when a color is selected. Changing the Preview Panel You should notice in the Color2 example that the color chooser has both a preview panel and a JLabel to show the intermediate results of color selection. Instead of adding your own label below the JColorChooser, you can hide or replace the existing preview panel. Hiding means that the chooser has no preview panel. Replacing means that the foreground color of the replacement component is changed when the user selects a new color in the chooser. To demonstrate, the following program, Color3, replaces the default preview panel with a new JLabel. Because the JLabel is part of the color chooser, it isn't necessary to attach a listener to the ColorSelectionModel to watch for changes. import java.awt.*; import java.awt.event.*; import javax.swing.*; import javax.swing.colorchooser.*; import javax.swing.event.*; public class Color3 { public static void main(String args[]) { Runnable runner = new Runnable() { public void run() { JFrame frame = new JFrame("Fourth Sample"); frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); Container contentPane = frame.getContentPane(); final JLabel label = new JLabel ("Hello, World", JLabel.CENTER); label.setFont( new Font("Serif", Font.BOLD | Font.ITALIC, 48)); label.setSize(label.getPreferredSize()); final JColorChooser colorChooser = new JColorChooser(Color.RED); colorChooser.setPreviewPanel(label); // For no preview panel, add a JComponent with no size // colorChooser.setPreviewPanel(new JPanel()); contentPane.add(colorChooser, BorderLayout.CENTER); frame.pack(); frame.setVisible(true); } }; EventQueue.invokeLater(runner); } } As the comments in the program indicate, if you want a chooser with no preview panel, add a JComponent subclass such as a JPanel with no size. Passing in null to the setPreviewPanel method does not remove the panel. Instead, it tells the ColorChooserComponentFactory to use the default panel. Working with Chooser Panels You might have noticed that the JColorChooser comes with three different ways of selecting colors: the Swatches tab, the HSB tab (for selecting color from Hue, Saturation, and Brightness levels), and the RGB tab (for selecting color from Red, Green, and Blue levels). If you don't like the default set of panels provided by these tabs, you can add your own. Each panel extends from the AbstractColorChooserPanel class. There are various support methods in JColorChooser that you use to create your own panels: o getChooserPanels() returns the current set of panels. o addChooserPanel(AbstractColorChooserPanel panel) adds a specific panel. o removeChooserPanel(AbstractColorChooserPanel panel) removes a specific panel. o setChooserPanels(AbstractColorChooserPanel[] panels) changes all the available panels at once, instead of adding and removing individual panels. The AbstractColorChooserPanel class consists of five abstract methods that you must implement to provide your own chooser panel: o buildChooser() o getDisplayName() o getLargeDisplayIcon() o getSmallDisplayIcon() o updateChooser() [protected] To demonstrate, lets create a color chooser panel that consists of a JComboBox with the class constants from the Color and SystemColor classes to choose from. In this example, the three get methods of the AbstractColor class are really easy to implement. For no icons, the getSmallDisplayIcon and getSmallDisplayIcon methods simply return null: public String getDisplayName() { return "SystemColor"; } public Icon getSmallDisplayIcon() { return null; } public Icon c() { return null; } The buildChooser method here creates the necessary JComboBox and handles color selection changes. When a selection changes, the ColorSelectionModel needs to be notified. In the code snippet below, the labels variable is an array of names for the color objects. The colors variable is an array of actual Color values. Here is the buildChooser method. Notice that it includes calls to some helper methods (the methods will be shown in the complete example). It also includes a "reserved" position at the end in case a color is chosen from a different chooser that doesn't match one of its own. protected void buildChooser() { comboBox = new JComboBox(labels); ItemListener listener = new ItemListener() { public void itemStateChanged(ItemEvent itemEvent) { int state = itemEvent.getStateChange(); if (state == ItemEvent.SELECTED) { int position = findColorLabel(itemEvent.getItem()); // last position is bad (not selectable) if ((position != NOT_FOUND) && (position != labels.length-1)) { ColorSelectionModel selectionModel = getColorSelectionModel(); selectionModel.setSelectedColor(colors[position]); } } } }; comboBox.addItemListener(listener); add(comboBox); } The updateChooser method is a bit simpler. It uses the inherited getColorFromModel method, and maps a color in the combo box to the current setting. public void updateChooser() { Color color = getColorFromModel(); setColor(color); } Here is the complete source for the color chooser panel. It might look overly complicated, but it isn't. import javax.swing.*; import javax.swing.colorchooser.*; import java.awt.*; import java.awt.event.*; public class MyColorChooserPanel extends AbstractColorChooserPanel { private static int NOT_FOUND = -1; JComboBox comboBox; String labels[] = { "BLACK", "BLUE", "CYAN", "DARK_GRAY", "GRAY", "GREEN", "LIGHT_GRAY", "MAGENTA", "ORANGE", "PINK", "RED", "WHITE", "YELLOW", "activeCaption", "activeCaptionBorder", "activeCaptionText", "control", "controlDkShadow", "controlHighlight", "controlLtHighlight", "controlShadow", "controlText", "desktop", "inactiveCaption", "inactiveCaptionBorder", "inactiveCaptionText", "info", "infoText", "menu", "menuText", "scrollbar", "text", "textHighlight", "textHighlightText", "textInactiveText", "textText", "window", "windowBorder", "windowText", ""}; Color colors[] = { Color.BLACK, Color.BLUE, Color.CYAN, Color.DARK_GRAY, Color.GRAY, Color.GREEN, Color.LIGHT_GRAY, Color.MAGENTA, Color.ORANGE, Color.PINK, Color.RED, Color.WHITE, Color.YELLOW, SystemColor.activeCaption, SystemColor.activeCaptionBorder, SystemColor.activeCaptionText, SystemColor.control, SystemColor.controlDkShadow, SystemColor.controlHighlight, SystemColor.controlLtHighlight, SystemColor.controlShadow, SystemColor.controlText, SystemColor.desktop, SystemColor.inactiveCaption, SystemColor.inactiveCaptionBorder, SystemColor.inactiveCaptionText, SystemColor.info, SystemColor.infoText, SystemColor.menu, SystemColor.menuText, SystemColor.scrollbar, SystemColor.text, SystemColor.textHighlight, SystemColor.textHighlightText, SystemColor.textInactiveText, SystemColor.textText, SystemColor.window, SystemColor.windowBorder, SystemColor.windowText, null}; // Change combo box to match color, if possible private void setColor(Color newColor) { int position = findColorPosition(newColor); comboBox.setSelectedIndex(position); } // Given a label, find the position of the label in the list private int findColorLabel(Object label) { String stringLabel = label.toString(); int position = NOT_FOUND; for (int i=0,n=labels.length; i