Enterprise Java Technologies Tech Tips Tips, Techniques, and Sample Code Welcome to the Enterprise Java Technologies Tech Tips for October 27, 2003. Here you'll get tips on using enterprise Java technologies and APIs, such as those in Java 2 Platform, Enterprise Edition (J2EE). This issue covers: * Introduction to JNDI * Writing an Entity Resolver These tips were developed using Java 2 SDK, Standard Edition, v 1.4 and Java 2 SDK, Enterprise Edition, v 1.3.1 (Reference Implementation). You can download the current Reference Implementation or the next release of the Reference Implementation, Java 2 SDK, Enterprise Edition, v 1.4 (currently a Beta release), at http://java.sun.com/j2ee/download.html. This issue of the Tech Tips is written by Mark Johnson, president of elucify technical communications (http://www.elucify.com/), and co-author of Designing Enterprise Applications with the J2EE Platform, 2nd Edition (http://java.sun.com/blueprints/guidelines/ designing_enterprise_applications_2e/). Mark Johnson runs an open forum for discussion of the tips at http://groups.yahoo.com/group/techtipsarchive/. You can view this issue of the Tech Tips on the Web at http://java.sun.com/jdc/EJTechTips/2003/tt1027.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. You can download the sample archive for these tips at http://java.sun.com/jdc/EJTechTips/download/ttoct2003.ear. The context root for the application is ttoct2003, and the index.html welcome file indicates how to use the sample code. Any use of this code and/or information below is subject to the license terms at http://developer.java.sun.com/berkeley_license.html - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - INTRODUCTION TO JNDI Many J2EE developers use the Java Naming and Directory Interface (JNDI) on a regular basis to look up environment entries, DataSource objects, JMS message destinations, and enterprise bean home interfaces. But many people simply copy, paste, and modify code that does these things without really understanding JNDI. This tip provides an introduction to using JNDI as a way to access the distributed resources in your enterprise. Enterprise applications, by their very nature, pull together distributed resources from multiple locations to support business operations. Services may come and go as new systems are created, existing systems are upgraded, and old systems are retired. Decoupling application services from one another makes a system easier to extend and maintain. But when services are decoupled, they need to be able to find one another to do their jobs. That's where naming services and directories become useful. A naming service provides a way to look up objects or references to objects by name. Examples of such objects include message queues, database connection factories, environment parameters, and distributed components such as enterprise beans. Application developers give names to objects by binding them to names within a naming service. Application code can then use the naming service to look up objects by their bound names. This decoupling means that network objects can be brought up and down for maintenance, requests can be redirected, and services can be reconfigured dynamically, all without any change to the system components that use them. You probably already have a good understanding of several existing naming services: o DNS (Domain Name Service) maps hostnames like java.sun.com to IP addresses like %nslookup java.sun.com%. o The COS (Common Object Services) naming service, used for CORBA (Common Object Request Broker Architecture), maps CORBA interface names to object interfaces. You can even consider a computer's filesystem as a naming service that maps a file's pathname to its contents. A mapping from a name to an object is called a binding. Bindings are created by the person who configures the naming service. Most naming services also provide a way for programs to bind and unbind names to objects at runtime. A context is a set of bindings of names to objects. For example, in a filesystem the pathname /home is often the context that contains the system's user directories. Contexts may contain other contexts. The user directories in the /home context are themselves contexts that contain user files. At the very least, a context has a naming convention and a lookup function. For example, DNS has a naming convention of strings separated by dots, with the least-significant string to the left, and the domain on the right. The lookup function for DNS can be accessed from the command line using the nslookup program. (Of course, there is also a programmatic interface to the DNS naming service.) Contexts often offer ways to bind and unbind objects, and to list their contents. Sometimes the objects in a naming service contain the data another program needs. For example, the objects representing environment entries in a J2EE application are often stored in the naming service. Other times, the object in the naming service represents a reference to the object. For instance, an object that provides a reference to a server is usually stored by the naming service as a reference to a server, not as an open server connection. The reference object returned by the naming service can be used to create a server connection when it is needed. A naming service that provides data about bound objects is called a directory. For example, filesystem directories typically offer information about the size, type, access permissions, and creation and modification dates of a file. Some directories allow lookup both by name or by combination of attributes. Although each naming service is well suited to its task, the way each naming service works is different. Each naming service has its own naming conventions, lookup, binding, and directory protocols, and object service interfaces. JNDI provides a consistent way to name and find network services. Java Naming and Directory Interface You might already know how to use JNDI to access network objects such as JDBC database connections, JMS queues, or enterprise bean home interfaces. Although JNDI does map names to objects, JNDI is not a naming service. Rather, it is a set of interfaces that wrap existing naming services, making them accessible in a standard way. Code in an application calls JNDI interface methods. The objects that implement these methods map the JNDI interface calls to calls on the underlying naming service. JNDI also defines a unified naming convention. JNDI names are mapped by JNDI's naming manager into names that conform to the underlying naming services' naming conventions. The package javax.naming contains the following naming and directory-related interfaces: o javax.naming.Context represents a naming context, which is used to look up and manage bindings and subcontexts. o javax.naming.Name provides an abstract representation of a name in a naming service. o javax.naming.Binding is a representation of a naming service name and the object to which the name is bound. o javax.naming.Reference is a representation of how to get a copy of an object. Looking Up a Context The sample code that accompanies this tip shows how to list the contents of a JNDI context. The sample servlet, Oct2003Servlet, looks up and displays the contents of the JNDI namespace for a name supplied by the user. The easiest way to get a context is to create an instance of class javax.naming.InitialContext. The sample servlet method jndiList creates an initial context, and uses it to look up a named object: InitialContext ic = new InitialContext(); Object objFound = ic.lookup(name); The name comes from the HTTP GET or POST variable name supplied by the user HTML page. If the object returned is a Context, jndiList calls method listContext. The listContext method lists the contents of the context at the given name. If the object is a DataSource, jndiList prints some information about the named data source. Method listContext prints a table of the contents of a given JNDI context. To do so, it uses Context method listBindings, which returns a NamingEnumeration. NamingEnumeration ne = context.listBindings(""); NamingEnumeration implements java.util.Enumeration. Method NamingEnumeration.next returns an object of type javax.naming.Binding, which contains the name of the object, the object's class name, and the stored object itself: while (ne.hasMore()) { Binding ncp = (Binding)ne.next(); String objName = ncp.getName(); String objClass = ncp.getClassName(); Object objObj = ncp.getObject(); ... } If you simply want the names and classnames in a Context, you can use method Context.list. That method also returns a NamingEnumeration, but the collection it iterates is of type NameClassPair instead of Binding. NameClassPair contains only the name and object class name. See the section "Running the Sample Code" for instructions on how to deploy and run the sample application. For more information about JNDI, see the JNDI Tutorial (http://java.sun.com/products/jndi/tutorial/index.html). - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - WRITING AN ENTITY RESOLVER If you write XML, you've certainly seen the symbols XML uses to represent characters such as ampersand (&) and less-than (<). These symbols are called entities. In XML, entities always begin with an ampersand and end with a semicolon. Entities are used to parameterize XML documents. This tip explains how to parameterize your XML documents by defining entities in the document's DTD. It also explains how to write an entity resolver that controls what text replaces entity references in an XML document. Defining Entities Any text that is repeated in multiple places in an XML document might be better represented as an entity. Entities can easily be defined in the document's DTD. An example of defining such an entity appears in the sample code file misc/data.xml: The entity can then be used in the XML itself, just as you would use & or <. Here's an example from the file data.xml: &helloWorld; &helloWorld; ... When an XML parser reads this text, it replaces everything between the ampersand and the semicolon with the contents of the entity: Hello, world! Hello, world! ... An entity whose value is defined directly in the DTD is called an internal entity. It's a lot like a #define in a C program. Internal entities are useful for placing repetitive text, such as disclaimers or copyright notifications, in a single place in a project. Updating the definition of the internal entity changes the replacement text everywhere. Including File Contents A second kind of entity, called an external entity, gets its replacement text from a file instead of from a string. An external entity is more like a #include in C. The replacement text for an external entity is the entire contents of a file. One kind of external entity uses a public identifier to name the entity, and a system identifier to tell the XML processing program where to find the replacement text. The sample code for this tip includes an XML file, data.xml, that defines four external entities in this way: Each entity has a unique name (its public id), that is the first string after the PUBLIC keyword. The second string is the system id -- it provides a system-specific identifier for where to find the replacement text. When an entity reference (such as &firstLine) is used in an XML document, the XML processor must know how to find the replacement text. This is called resolving the entity reference. By default, most XML processors try to use the system ID, as either a URL or a filesystem pathname, to retrieve the entity's replacement text. Sometimes, though, you need to take control of how the XML processor resolves the reference. For example, what if the system id is a URL, but you're not connected to the Internet? The SAX interface, on which SAX and DOM parsers are based, allows you to control the replacement text for an external entity by defining an entity resolver class. Resolving Entity References An entity resolver implements interface org.sax.xml.EntityResolver. It has a single method, resolveEntity, whose signature is as follows: public InputSource resolveEntity(String publicID, String systemID) throws SAXException; The method receives as arguments the public id and system id of the entity, which are the first and second arguments after the PUBLIC keyword in the declaration above. It returns an object that implements interface org.sax.xml.InputSource. An entity resolver uses the public and system ids to locate the replacement text for the entity, and returns an InputSource object that the parser accesses to get the replacement text. The sample code for this tip also includes an entity resolver, PropertyEntityResolver, that reads a Java property file using the Properties class. The code that loads the file and retrieves the replacement text appears below: // Load properties from a URL. protected void loadProps(String urlString) throws MalformedURLException, IOException { URL url = new URL(urlString); URLConnection con = url.openConnection(); _props = new Properties(); _props.load(con.getInputStream()); } // Given the name of a property, // return the property's value public InputSource resolveEntity( String publicID, String systemID) throws SAXException { // Load properties from URL referenced // by system id. // Assume system id is the same for all symbols. if (_props == null) { try { loadProps(systemID); } catch (Exception e) { throw new SAXException(e); } } // Extract property name from public ID int index = publicID.lastIndexOf("//"); String value = "???PropertyNotFound???"; if (index >= 0) { value = _props.getProperty( publicID.substring(index + 2)); } StringReader sr = new StringReader(value); return new InputSource(sr); } When the sample class EntityResolverDemo creates its DOM parser, it sets the entity resolver for the class to an instance of PropertyEntityResolver, like this: // Create the parser, and set the error handler // and entity resolver DocumentBuilder db = dbf.newDocumentBuilder(); db.setErrorHandler(this); PropertyEntityResolver propertyEntityResolver = new PropertyEntityResolver(); db.setEntityResolver(propertyEntityResolver); When the parser encounters an external entity reference, it calls PropertyEntityResolver.resolveEntity, and receives as a response an InputSource containing the replacement text. It then reads the replacement text and substitutes it in the result XML where the entity reference occurred. See the section "Running the Sample Code" for instructions on how to deploy and run the sample application. You'll find instructions for running the ERDemo program on the index page. Try running the sample program with the data.xml file specifying the file english.props. To do that: 1. Extract the data files from the entity resolver jar file, ERDemo.jar: jar xvf ERDemo.jar misc 2. Put ERDemo.jar in your CLASSPATH. 3. Run the example: java ERDemo misc/data.xml When you run the ERDemo program, it should generate an XML document with English replacement text like this: Hello, world! Hello, world! Line 1: This line is written in English. Line 2: How are you? Line 3: What would you like to eat? Last line: I must say goodbye. Then edit data.xml and replace "english.props" with "spanish.props" in all of the The parsed text will change to the following: Hello, world! Hello, world! Line 1: Esta linea esta escrito en Espagnol. Line 2: Como estas? Line 3: Que quieres cenar? Last line: Hay que decir adios! The sample code also includes property files in French and German. Try running the sample code using those property files too. For more information about JNDI, see the JNDI Tutorial (http://java.sun.com/products/jndi/tutorial/index.html). - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - RUNNING THE SAMPLE CODE Download the sample archive for these tips (from http://java.sun.com/jdc/EJTechTips/download/ttoct2003.ear). The application's context root is ttoct2003. The downloaded ear file also contains the complete source code for the sample. To run the sample program, follow these steps: 1. Be sure that both the j2ee server and database server are running. For the J2EE Reference Implementation, execute both: j2ee -verbose and cloudscape -start 2. Download the ear file as described above. 3. Deploy the ear file to your server. You can deploy the application archive (ttoct2003.ear) in the J2EE Reference Implementation using the deploytool program: $J2EE_HOME/deploytool -deploy ttoct2003.ear localhost Replace localhost with the name of the host on which the server is installed. For a standard installation on a single machine, the hostname typically (and literally) is localhost. 4. Access the application at http://localhost:8000/ttoct2003. For a J2EE-compliant implementation other than the Reference Implementation, use your J2EE product's deployment tools to deploy the application on your platform. See the index.html welcome file for instructions on running the application. . . . . . . . . . . . . . . . . . . . . . . . IMPORTANT: Please read our Terms of Use, Privacy, and Licensing policies: http://www.sun.com/share/text/termsofuse.html http://www.sun.com/privacy/ http://developer.java.sun.com/berkeley_license.html * FEEDBACK Comments? Send your feedback on the Enterprise Java Technologies Tech Tips to: http://developers.sun.com/contact/feedback.jsp?category=sdn * SUBSCRIBE/UNSUBSCRIBE Subscribe to other Java developer Tech Tips: - Core Java Technologies Tech Tips. Get tips on using core Java technologies and APIs, such as those in the Java 2 Platform, Standard Edition (J2SE). - Wireless Developer Tech Tips. Get tips on using wireless Java technologies and APIs, such as those in the Java 2 Platform, Micro Edition (J2ME). To subscribe to these and other JDC publications: - Go to the JDC Newsletters and Publications page, (https://softwarereg.sun.com/registration/developer/en_US/subscriptions), choose the newsletters you want to subscribe to and click "Update". - To unsubscribe, go to the subscriptions page, (https://softwarereg.sun.com/registration/developer/en_US/subscriptions), uncheck the appropriate checkbox, and click "Update". - To use our one-click unsubscribe facility, see the link at the end of this email: - ARCHIVES You'll find the Enterprise Java Technologies Tech Tips archives at: http://developer.java.sun.com/developer/EJTechTips/index.html - COPYRIGHT Copyright 2003 Sun Microsystems, Inc. All rights reserved. 901 San Antonio Road, Palo Alto, California 94303 USA. This document is protected by copyright. For more information, see: http://java.sun.com/jdc/copyright.html Enterprise Java Technologies Tech Tips October 27, 2003 Trademark Information: http://www.sun.com/suntrademarks/ Java, J2SE, J2EE, J2ME, and all Java-based marks are trademarks or registered trademarks of Sun Microsystems, Inc. in the United States and other countries.