Enterprise Java Technologies Tech Tips Tips, Techniques, and Sample Code Welcome to the Enterprise Java Technologies Tech Tips for July 30, 2004. 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: * Using Existing Web Resources * Using TrAX for XPath These tips were developed using the Java 2, Enterprise Edition, v 1.4 SDK. You can download the SDK at http://java.sun.com/j2ee/1.4/download-dr.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/developer/EJTechTips/2004/tt0730.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/developer/EJTechTips/download/ttjul2004.ear. Any use of this code and/or information below is subject to the license terms at http://developers.sun.com/dispatcher.jsp?uid=6910008. 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. - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - USING EXISTING WEB RESOURCES With all of the attention being paid to SOAP-based XML Web services, it's easy to forget that there are a lot of useful services and content out there that are not available with SOAP. The following tip shows how to use a servlet to use existing Web resources from the Web tier. In this case, the existing Web resource is a non-standards-based XML service. Leveraging Existing Resources Most content on the Web isn't available in the form of standards-based Web services -- at least not yet. Much static content exists in the form of existing HTML pages, text files, document files (such as PDF files and word processor documents), files on ftp sites, and images. Some services actively produce XML in response to HTTP POST or GET requests. Other services, such as RSS feeds, have static URLs, but act as services because their data is dynamic, and their content is usually used by programs rather than directly by human readers. Enterprise Java technology is excellent for interoperating with existing resources, both in organizational intranets and on the Internet at large. Enterprise Java technology can be used in any tier to integrate existing information assets into new systems. You don't need to wait for SOAP-based Web services to create valuable new systems that integrate existing resources. Search Engines at Work The National Library of Medicine is one of the National Institutes of Health, a US federal government agency. One of NLM's services is PubMed*, a searchable database of biomedical publication abstracts. PubMed's search engine is called Entrez. You can use Entrez to search the abstracts at http://www.ncbi.nlm.nih.gov/entrez/query.fcgi, or just type pubmed into the address bar of your browser and hit return. PubMed also offers a service called e-utilities that provides a programmatic interface to the search engine. e-utilities receive HTTP GET requests from clients and can return XML that represents search results. The sample code that accompanies this tip is a servlet that accesses two of the e-utilities: o esearch. Performs a search and returns a list of IDs. o efetch. Fetches requested documents in a variety of formats. You also have the option of specifying that the XML returned by either esearch or efetch be transformed by an XSLT stylesheet for presentation in the browser. Depending on what parameters are provided by the client, the servlet uses an appropriate stylesheet. An Overview of the Servlet The sample code servlet, called Jul2004Servlet, uses the existing Web services esearch and efetch to provide new functionality. The servlet converts its input parameters into GET URLs, and uses them to retrieve data for analysis and display. The servlet first calls esearch to execute a search, and then calls efetch to retrieve results. 1. The servlet receives POST parameters from a user form and builds an HTTP GET URL with the parameters required by esearch. 2. The servlet executes HTTP GET with the URL. The esearch e-utility sends back XML containing data that identifies the server-side result set. 3. The servlet parses the XML returned by esearch, and uses DOM API calls to retrieve the WebEnv and QueryKey. It then uses these values to build a URL to get the data. 4. The servlet executes another HTTP GET, this time to efetch. It indicates the result set, and includes formatting parameters specified in the original request. 5. The servlet receives the requested document data from efetch, and optionally transforms the data with XSL before returning a result to the browser. Searching the Database The first step in retrieving document information from the Entrez server is to perform an esearch with several parameters from the input form in the application's index.html page. The esearch service executes the query on the Entrez server and returns data corresponding to the results of the query. If the HTTP GET query string includes the parameter "usehistory=y", the Entrez server also returns two additional items of data: a WebEnv string and a QueryKey. A WebEnv string is a unique identifier for user state within a session in the Entrez server. This state includes a history of previous queries and their result sets. A QueryKey is a small integer that identifies the specific query within the session. Together, the WebEnv and the QueryKey represent the query result set on the server. The servlet receives several parameters from the input form in the application's index.html page. The servlet code uses the POST parameters to build a URL, like this: AbstractMap paramMap = new TreeMap(); res.setContentType("text/html"); OutputStream os = res.getOutputStream(); // Get parameters String query = req.getParameter("query"); // Execute esearch // db=PubMed: search PubMed database // usehistory=y: return key to server-side result set // term=$query: search for "$term" in PubMed paramMap.put("db", "PubMed"); paramMap.put("usehistory", "y"); paramMap.put("term", query); // Create the URL and get its content String surl = buildUrl(BASEURL_ESEARCH, paramMap); InputStream is = getContent(surl); The buildUrl method used here takes a base URL and constructs an HTTP GET URL by creating a key=content string for each (key, content) pair in the map. The content is URL-encoded, and the parameters are joined with the string "&" to produce an HTTP GET URL. For example, if the form query is "neanderthal DNA", then the query string will be: db=PubMed&term=neanderthal+dna&usehistory=y The method getContent simply opens an HttpUrlConnection to the requested URL and returns an InputStream to the resulting content, as follows: protected InputStream getContent(String surl) throws ServletException { Object content = null; try { // Connect to URL URL url = new URL(surl); HttpURLConnection conn = (HttpURLConnection)url.openConnection(); conn.connect(); // Get content as stream content = conn.getContent(); } catch (Exception e) { throw new ServletException(e); } return (InputStream)content; } The input stream from an esearch request contains an XML document that looks like this: 19 19 0 1 0ed8yFoq_CFyEEP6hW9aZ9UoTCVrrPm2w343S9MRNaT9MQmwbnjI The servlet needs to extract the content of the QueryKey and WebEnv elements in this XML document. It needs to include this content in a subsequent call to efetch, which will return the document data for display. Getting Results Because efetch results are small, it can be parsed into an in-memory DOM tree. The servlet parsing the string returned by esearch, and in doing so, uses a DocumentBuilder to get the Document object into memory: // Create DOM parser and parse search result DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance(); DocumentBuilder db = dbf.newDocumentBuilder(); Document esearchDoc = db.parse(is); // Get WebEnv, Count, and QueryKey from result // WebEnv - result key // QueryKey - history index // Count - result set length String webenv = getElementString(esearchDoc, "WebEnv"); String count = getElementString(esearchDoc, "Count"); String querykey = getElementString(esearchDoc, "QueryKey"); The getElementString method is a simple convenience function that finds a node of the given name and returns its first Text subnode. The servlet extracts the WebEnv and QueryKey values from the parsed DOM Document. At this point, the servlet has a result set waiting for it on the server. The next step is to retrieve and format the data using efetch. Specifying Fetch Parameters Efetch requires just a few parameters to identify the results of an esearch: o db. Identifies the database ("PubMed") o WebEnv. Identifies the session o query_key. Identifies the query in the session Additional parameters limit the size, and control the format of the returned data: o retstart and retmax tell efetch to return a subset of the result set, starting at retstart and returning no more than retmax records. Without these parameters, efetch returns the entire result set, which can often be multiple megabytes. o retmode indicates how to format the data (XML, HTML, text, or ASN.1). The default is ASN.1, PubMed's native storage and exchange format. o rettype tells what to return for each record. By default, efetch returns abstracts. The servlet creates a Map of request parameters, just as it did for esearch. It uses the WebEnv and QueryKey strings it retrieved from esearch, and also includes several parameters received from the original form. The getParameter method simply gets a parameter from the request, substituting a default string if the parameter is not set. paramMap = new TreeMap(); paramMap.put("WebEnv", webenv); paramMap.put("query_key", querykey); paramMap.put("db", "PubMed"); paramMap.put("retstart", getParameter(req, "start", "0")); paramMap.put("retmax", getParameter(req, "max", "20")); paramMap.put("retmode", retmode = getParameter(req, "retmode", "xml")); paramMap.put("rettype", getParameter( req, "rettype", "abstract")); // Create URL and get its content surl = buildUrl(BASEURL_EFETCH, paramMap); is = getContent(surl); The servlet uses the map to build a request URL, and uses getContent to get an InputStream to the request result. Transforming the Document If the data format requested was not XML, or if there was no valid stylesheet, the servlet simply copies the data returned by efetch to the browser. This feature can be useful if you are experimenting with e-utilities directly. If the data format is XML, and if there is a valid, nonempty stylesheet, the servlet applies this stylesheet to the data and returns the result to the browser. The XML returned to the browser contains quite a bit of data for each record. Here is an excerpt of the returned data:
The thermal history of human fossils and the likelihood of successful DNA amplification. This data is the content on which the stylesheet operates to produce its output. There are two ways for a user to specify a stylesheet to apply. The user form has a radio button called "isFile" that indicates what stylesheet to use for the transformation. If isFile is 1, the parameter "stylesheet" contains the name of a stylesheet file in the Web application archive (WAR file). If isFile is 0, the parameter sstext contains a stylesheet that the user typed into a TEXTAREA in the form. You can use this feature to view formatted reports. You can also use the feature to create new reports by copying XSL from a text editor directly into the form. The code that performs the transform receives the following: o An input stream to read from (the results of efetch) o An output stream to write to (the servlet's output) o The isFile parameter o A string containing either the name of the stylesheet or the text of the stylesheet itself. The method builds a Transformer object from the stylesheet, and then calls the Transformer's transform method, passing the input and output streams as source and destination. // Create XSLT transformer for output. TransformerFactory tf = TransformerFactory.newInstance(); Transformer t = null; // Use identity transform if no transformer was specified if (stylesheet == null || stylesheet.equals("")) { t = tf.newTransformer(); } else { StreamSource xslsource = null; if (isFile) { // Read XSL stylesheet from app archive and wrap it as // a StreamSource. Then use it to construct // a transformer. InputStream xslstream = _config.getServletContext(). getResourceAsStream(stylesheet); xslsource = new StreamSource(xslstream); } else { // Read XSL stylesheet from string xslsource = new StreamSource( new StringReader(stylesheet)); } t = tf.newTransformer(xslsource); } // Do transform from input (e-utilities result) to // output (servlet writer) StreamSource ss = new StreamSource(is); StreamResult sr = new StreamResult(os); t.transform(ss, sr); The completed transform writes the result HTML to the output stream, and the server delivers the resulting content to the browser. Viewing the Results The application archive contains two stylesheets: o titles.xsl displays only the titles of the documents. o extended.xsl formats each record as a citation in the standard bibliographic reference format defined by the National Library of Medicine. In addition, the actual citation information (journal, publication date, volume, issue, and page numbers) is formatted as a hyperlink to the entire abstract at the PubMed site. You can also define your own styles using the stylesheet embedded in the input form. The form's stylesheet TEXTAREA provides the skeleton of a stylesheet. Perform a sample search using the servlet. Specify no stylesheet. Copy the resulting XML to a file, and use it as example XML to develop a new stylesheet in a text editor. Then copy and paste the entire stylesheet into the form. Click the "Using custom XSL style:" radio button, and select Search. If your stylesheet is valid, the servlet will use it to format your search results. It would be easy to expand this example servlet into something more useful. For example, you could download and analyze large data sets from PubMed. Or, you could use the other databases available from e-utilities (see the einfo utility for more) to write bioinformatics applications, displaying DNA sequences with the publications that link to them. - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - USING TRAX FOR XPath - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - The expressions in "match" and "select" attributes of XSL stylesheets are written in the XML Path Language, or XPath. (http://www.w3.org/TR/xpath). An XPath expression addresses a set of nodes in a DOM document tree. XPath is a powerful expression language for writing effective XSLT stylesheets. A variety of XPath implementations are available, four of which are: o The open-source, portable XPath implementation called Jaxen is fast, and was included by Sun in the JavaServer Pages Standard Tag Library (JSTL) and the Java Web Services Developer Pack. o The Java 2 Platform, Standard Edition (J2SE) 1.5 (currently available as a Beta 2 release) offers a new XPath class that executes XPath searches against a DOM Node. This new interface is part of Java API for XML Processing (JAXP) 1.3, (JSR 206) (http://java.sun.com/j2se/1.5.0/docs/api/javax/xml/xpath/XPath.html) o Both JDOM and dom4j offer XPath interfaces. (http://www.dom4j.org/apidocs/org/dom4j/xpath/package-summary.html) (http://www.jdom.org/docs/apidocs/org/jdom/xpath/package-summary.html) o Apache's Xalan package includes a package for XPath. The Xalan package is included in Sun's J2SE runtime (in rt.jar), so you can use Xalan's XPath API if you're running that distribution. But that solution isn't portable, because the J2EE specification doesn't require Xalan to be present in all distributions. (http://xml.apache.org/xalan-j/apidocs/org/apache/xpath/package-summary.html) All of these options are portable and offer reasonable performance. But in a standard J2EE 1.4 installation, you can't depend on any of the above to be available. And you can't always redistribute third-party libraries. Fortunately, even you can't use the above solutions, there's still a way to get XPath support: by using TrAX (Transformation API for XML), which is included as part of the current J2EE specification. An XPath Stylesheet The TrAX API defines interfaces for classes that transform XML. The most common use for the API is to create, use, and manage XSLT stylesheets. Creating XSLT stylesheets is covered in the October 15, 2002 Tech Tip titled "Transforming XML With XSL." XSLT stylesheets are also used in the first tip in this issue "Using Existing Web Resources." A TrAX Transformer uses an XPath matcher to match the XPath patterns in an XSLT stylesheet against the Transformer's input document. You can't access the Transformer's XPath API because it isn't standard. But you can write a stylesheet that matches the pattern you want.The stylesheet then copies the matching node, and all of its children. Let's look at an example. Imagine that you wanted to match the pattern '/html/head/title'. The following XSLT stylesheet matches that XPath pattern, and copies the matching node and all of its children to its result. \n This stylesheet has two templates: o. The first template matches the requested XPath, and copies it and all of its children to the result (that's how works). o. The second template overrides XSLT default templates, so that only data produced by the first template appears in the final result set. This template is necessary because default XSLT templates copy all text and nonignorable white space nodes to the result. This template overrides these default templates, and instead ignores all nodes not matched by the first template. You can replace the pattern in the first template with any XPath pattern. The result will be all nodes in the document that match the replacement pattern. Keep in mind that this solution trades performance for portability. This XPath stylesheet approach has several drawbacks. The stylesheet introduces additional processing. Also, because it loads the entire document as a DOM tree, the approach isn't suitable for very large documents. And, because the code returns clones of nodes from the original DOM, you can't use the results of the XPath searches to edit the original tree. However, when performance is not an issue, and you can't use one of the other solutions, this tip offers a portable, standards-based workaround. This tip's sample code creates a stylesheet like the one above, with a user-defined XPath expression as the "match" attribute of the first template. It then compiles and executes the stylesheet against an input document, producing a NodeList of results. Sample Code The main method in the sample code class TrAXPath requires at least two arguments: o A source for the XML to transform; either a filename or a URL o One or more XPath patterns to apply to the source XML The main method parses the input XML into a DOM. It then creates a TrAXPath object for each XPath expression, and uses it to get a list of matching nodes for the expression. Finally, it prints the nodes that matched: // Match xpath against document TrAXPath txp = new TrAXPath(xpath); NodeList matched = txp.selectNodeList(doc); // Summarize nodes p("Match[" + xpath + "]"); for (int q = 0; q < matched.getLength(); q++) { p("--------------------[" + q + "]--------------------"); printDOM(matched.item(q)); p(""); } (p is a convenience method for System.out.println.) The TrAXPath class represents a single XPath expression, against which multiple XML documents can be matched. Each call to selectNodeList(Node) returns a list of Nodes that match the XPath expression in the TrAXPath object. A TrAXPath object is constructed with a String that contains the XPath. The constructor builds an XSLT stylesheet in memory, and then compiles it into a Templates object: public TrAXPath(String xpath) throws TransformerConfigurationException { StringBuffer sb = new StringBuffer(); // Create stylesheet that copies matching nodes sb.append("\n"); sb.append(""); sb.append(""); sb.append(""); sb.append(""); sb.append(""); sb.append(""); sb.append(""); sb.append(""); sb.append(""); // Construct the stylesheet Templates object. TransformerFactory tf = TransformerFactory.newInstance(); String stylesheet = sb.toString(); Reader r = new StringReader(stylesheet); StreamSource ssrc = new StreamSource(r); // Create templates object _templates = tf.newTemplates(ssrc); } You might be familiar with using a TransformerFactory to create a Transformer which executes a transform. You can also use a TransformerFactory to create a javax.xml.transform.Templates object which represents a compiled stylesheet. If you use a stylesheet more than once, it's good practice to create Transformers using a Templates object. In this way the stylesheet needs to be compiled only once. The real work in class TrAXPath occurs in method c. The main method uses selectNodeList to match patterns against its input XML. The selectNodeList method uses the _templates object to create a Transformer: // Return a Node that matches xpath in context. public NodeList selectNodeList(final Node context) throws TransformerConfigurationException, ParserConfigurationException, TransformerException { Transformer t = _templates.newTransformer(); The method then creates a DOMResult (required as the output of the transformer) containing a single DocumentFragment: // Create result document with top element "result" DocumentFragment df = DocumentBuilderFactory. newInstance(). newDocumentBuilder(). newDocument(). createDocumentFragment(); Result result = new DOMResult(df); The TrAXPath code uses a DocumentFragment instead of a Document because the result needs to be a list of nodes. A Document can only have one top-level node. An error will occur if you pass an empty document to the Transformer, and there is more than one Node. A DocumentFragment result does not have this limitation. Finally, the selectNode method uses the Transformer created using the in-memory stylesheet to transform the source Node (the Node being searched). The transformer copies all nodes that match the XPath into the DocumentFragment. Calling the DocumentFragment's getChildNodes method returns a NodeList of all of the nodes that matched the XPath expression: // Transform result into DocumentFragment t.transform(new DOMSource(context), result); // Return list of nodes in DocumentFragment return df.getChildNodes(); } Experimenting With The Code The second form on the application's index.html page communicates with a servlet to search a built-in XML file (the first act of William Shakespeare's "A Comedy of Errors") for XPaths that you enter. For example, to search for the title of the play in the input file, type the XPath expression "/PLAY/TITLE" into the search box and hit Search. The servlet produces the following result: The Comedy of Errors You can see the entire file by searching for "/", which matches (and prints) the top node of the document. You can also download the TrAXPath class, which has its own main method. Download the class file by clicking the download link on the form, and save the file to your current directory. Be sure the current directory is in your CLASSPATH. You can then execute XPath expressions against any XML file, like this: $ java com.elucify.tips.jul2004.TrAXPath xmlsource xpath The parameter xmlsource can be either the local filesystem name or the URL of an XML file. Consider the following sample XML: This is /a/b This is /a/c/b This is /a/d/b (first) This is /a/d/b (second) This is /a/e/b/b. What will happen? If you run the TrAXPath program against this XML tree, and search for the pattern "//b" ("b" anywhere in the tree), it produces the following results: Match[//b] --------------------[0]-------------------- This is /a/b --------------------[1]-------------------- This is /a/c/b --------------------[2]-------------------- This is /a/d/b (first) --------------------[3]-------------------- This is /a/d/b (second) --------------------[4]-------------------- This is /a/e/b/b. What will happen? The matcher matched all b nodes--or did it? Why didn't it match the node inside the other node in match 4? The matcher didn't find the inside of the other because of the way the first template works. As soon as it matches , the matcher copies that node and all of that node's children to the output. But it doesn't then check any of the children. So those child nodes are never seen by the matcher. If you want to match against all occurrences of -- even those inside another -- you could modify the first template slightly, like this: The new rule (apply-templates) tells the stylesheet processor to check and process all of the current node's children. Which behavior you prefer depends on your application requirements. The sample code for this tip also accepts as the XML source a URL instead of a file. This makes the TrAXPath program useful for grabbing chunks of data from the Internet. For example, you can run the program with an RSS feed: $ java com.elucify.tips.jul2004.TrAXPath \ http://servlet.java.sun.com/syndication/rss_java_highlights-10.xml \ '//item/title/text()' Match[//item/title/text()] --------------------[0]-------------------- Java Live Chat: Java Web Services Developer Pack 1.4. --------------------[1]-------------------- The JVMPI Transition to JVMTI --------------------[2]-------------------- What's Next? A Conversation about Web Communication with XML Pioneer, Tim Bray --------------------[3]-------------------- Upcoming Java Live Chat: Sun Game Server Technology. --------------------[4]-------------------- The Utility Model for Online Games: Part Two of a Conversation --------------------[5]-------------------- Get Ready for SOA --------------------[6]-------------------- 2004 JavaOne: Nokia Platinum Session --------------------[7]-------------------- Deploying Web Services: Grid Computing --------------------[8]-------------------- The J2EE 1.4 SDK and Sun Java Application Server Platform Edition 8 --------------------[9]-------------------- The Sun Java Desktop System, Release 2 Arrives Use this trick to address portable address DOM nodes until the Java 1.5 XPath class becomes standard for J2EE. - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - RUNNING THE SAMPLE CODE Download the sample archive for these tips (from http://java.sun.com/jdc/EJTechTips/download/ttjul2004.ear). The application's context root is ttjul2004. The downloaded EAR file also contains the complete source code for the sample. You can deploy the application archive (ttjul2004.ear) in the J2EE Reference Implementation using the deploytool program: $J2EE_HOME/deploytool -deploy ttjul2004.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. You can access the application at http://localhost:8000/ttjul2004. 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.jsp welcome file for instructions on running the application. . . . . . . . . . . . . . . . . . . . . . . . Please read our Terms of Use and Licensing policies: http://www.sun.com/share/text/termsofuse.html http://developers.sun.com/dispatcher.jsp?uid=6910008 PRIVACY STATEMENT: Sun respects your online time and privacy (http://sun.com/privacy). You have received this based on your e-mail preferences. If you would prefer not to receive this information, please follow the steps at the bottom of this message to unsubscribe. * 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://java.sun.com/developer/EJTechTips/index.html - COPYRIGHT Copyright 2004 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/developer/copyright.html Enterprise Java Technologies Tech Tips July 30, 2004 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. * The author of this tip, Mark Johnson, is a contract systems analyst at the National Center for Biotechnology Information (NCBI). The use of PubMed's interfaces in the tip is not an endorsement of PubMed by Sun.