Получение файла XML
Первым этапом процесса настройки вашего web-сайта на использование материалов информационных web-синдикатов является получение исходного файла XML от поставщика. В нашем примере мы обращаемся на сайт www.moreover.com, указывая параметр поиска, который генерирует данные по нашим заранее выбранным категориям сообщений. Ниже приведен полный URL-адрес:
www.moreover.com/cgi-local/page?wbrogden@bga.com+xml
После получения доступа к этому ресурсу на сервере Moreover.com специальное приложение возвращает текстовый поток в формате XML, содержащий заголовки по выбранным темам. В листинге 9.1 показаны заголовок и первый элемент в том виде, в котором они были исходно получены при загрузке файла XML. Обратите внимание на то, что строка DOCTYPE ссылается на определение DTD, расположенное на сайте Moreover.com. Чтобы обеспечить возможность анализа файла XML во время тестирования без доступа к web-сайту Moreover.com, класс XMLgrabber модифицирует строку DOCTYPE так, чтобы она ссылалась на определение DTD как на локальный файл.
Листинг 9.1. Заголовок и первый элемент загруженного файла XML (xmldump.xml)1
<?xml version="1.0" encoding="iso-8859-1"?> <!DOCTYPE moreovernews SYSTEM "moreovernews.dtd"> <moreovernews> <article id="_8510757"> <url>http://c.moreover.com/click/here.pl?x8510756</url> <headline_text>Cyclone Commerce Poised to Fulfill Promise of E-Signature Legislation</headline_text> <source>Java Industry Connection</source> <media_type>text</media_type> <cluster>Java news</cluster> <tagline> </tagline> <document_url> http://industry.java.sun.com/javanews/more/hotnews/ </document_url> <harvest_time>Jul 25 2000 8:34AM</harvest_time> <access_registration> </access_registration> <access_status> </access_status> </article>
Обратите внимание, что элемент, который в Moreover.com называется cl uster, идентифицирует основную тематическую категорию, к которой относится данное сообщение. В приведенном ниже примере сервлета мы используем только элементы url, headline_text, source и cluster. Определение DTD moreovernews, как видно из листинга 9.2, устроено относительно просто.
Листинг 9.2. Файл moreovernews.dtd (moreovernews.dtd)
<!ELEMENT moreovernews (article*)> <!ELEMENT article (url,headline_text,source,media_type, cluster,tagline,document_url,harvest_time, access_registration,access_status)> <!ATTLIST article id ID #IMPLIED> <!ELEMENT url (#PCDATA)> <!ELEMENT headline_text (#PCDATA)> <!ELEMENT source (#PCDATA)> <!ELEMENT media_type (#PCDATA)> <!ELEMENT cluster (#PCDATA)> <!ELEMENT tagline (#PCDATA)> <!ELEMENT document_url (#PCDATA)> <!ELEMENT harvest_time (#PCDATA)> <!ELEMENT access_registration (#PCDATA)> <!ELEMENT access_status (#PCDATA)>
Сервлет, который используется в рассматриваемом в этой главе приложении, включает в себя класс NetNewsSuper. Этот класс запускает поток Thread, который создает объект XMLgrabber для загрузки файла с наиболее свежими заголовками новостей, а также текущий файл DTD. В листинге 9.3 показано начало этого класса, в том числе его конструктор. Заметим, что конструктор снабжен URL- адресом искомого ресурса, а также содержит путь и имя файла, который будет использоваться для локальной копии.
Листинг 9.3. Начало класса XMLgrabber (XMLgrabber.java)
package com.XmlEcomBook.Chap09;
import java.net.* ; import java.io.* ; import java.util.*;
public class XMLgrabber { String source ; // complete URL to run std query // example "http://www.moreover.com/cgi-local/ page?wbrogden@bga.com+xml"; String saveDir ; // for both temp and final xml file String tfnxml ; // temp file name - see createTempXmlWriter String tfndtd ; // temp file name for dtd String saveName ; // for xml String dtdURL ; // complete DTD url from <!DOCTYPE line String dtdFname ; // for dtd
PrintWriter pw ; URL theURL ; Thread queryT ;
// all files from a given source will go to dest directory XMLgrabber( String src, String dest, String fname ){ source = src ; saveDir = dest ; saveName = fname ; // System.out.println("XMLgrabber initialized for " + src ); }
Поскольку мы не можем всегда рассчитывать на быстрое соединение с сайтом Moreover.com и так как в некоторых случаях попытка получить новые данные может провалиться, получение данных XML осуществляется отдельным от функций сервлета потоком (объектом Thread). Более того, файл со старыми данными не заменяется сразу же новым файлом; вместо этого полученные данные записываются во временный файл. Только когда мы удостоверимся, что полученный файл XML содержит все необходимые данные, мы удаляем старый файл и присваиваем имя временному файлу. Как показано в листинге 9.4, метод doQueryNow создает структуру для получения наиболее свежей информации. Помимо получения файла XML, содержащего заголовки сообщений, мы также пытаемся получить самое последнее определение DTD.
Листинг 9.4. Метод doQueryNow (XMLgrabber.java)
// run by external Thread to get file resident // return true if suceeds public boolean doQueryNow() throws IOException { theURL = new URL( source ); createTempXmlWriter(); grabXml(); if( !renameTemp( tfnxml, saveName )) return false ; tfnxml = null ; // now for the dtd if( dtdURL == null ) return true ; // System.out.println("Start DTD retrieval"); theURL = new URL( dtdURL ); createTempDtdWriter(); grabDtd(); boolean ret = renameTemp( tfndtd,dtdFname ); tfndtd = null ; return ret ; }
Существует несколько возможных ситуаций, в которых этот метод дает сбой: либо в случае, когда при попытке получить доступ к исходному файлу возникает исключение lOException, либо в случае неполадок с локальной файловой системой. Чтобы не загромождать локальный диск временными файлами, которые были созданы, но не прошли надлежащей обработки, для работы с файлами tfnxml и tfndtd разработана специальная методика.
Файл tfnxml создается при вызове метода createTempXmlWriter. Заметим, что если не возникнет исключительных ситуаций и переименование файла tfnxml в saveName пройдет успешно, переменная tfnxml устанавливается равной null. В противном случае метод finalize (листинг 9.9) попытается удалить временный файл. Файл tfndtd, предназначенный для загрузки DTD, проходит такую же обработку.
В листинге 9. 5 показаны методы объекта XMLgrabber, которые применяются для создания и управления временными файлами.
Листинг 9.5. Методы для управления временными файлами (XMLgrabber.java)
// saveDir used for all private boolean renameTemp(String tmp, String saveN ) { File src = new File( saveDir, tmp ); File dest = new File( saveDir, saveN); dest.delete(); return src.renameTo( dest ); }
private void createTempXmlWriter() throws IOException { tfnxml = "$" + Integer.toString((int) System.currentTimeMillis()) + ".xml"; File tfile = new File( saveDir, tfnxml ); while( tfile.exists() ){ // hunt for unique name tfnxml = tfnxml + "X" ; tfile = new File( saveDir, tfnxml ); } // ok, unique file name in tfnxml, File tfile set up FileWriter fw = new FileWriter( tfile.getAbsolutePath() ); pw = new PrintWriter( fw ); }
private void createTempDtdWriter() throws IOException { tfndtd = "$" + Integer.toString((int) System.currentTimeMillis()) + ".dtd"; File tfile = new File( saveDir, tfndtd ); while( tfile.exists() ){ // hunt for unique name tfndtd = tfndtd + "X" ; tfile = new File( saveDir, tfndtd ); } // ok, unique file name in tfndtd, File tfile set up FileWriter fw = new FileWriter( tfile.getAbsolutePath() ); pw = new PrintWriter( fw ); }
Как показано в листинге 9.6, метод grabXml считывает строки текста из входного потока, создав соединение по нужному URL-адресу. Строка заголовка XML, содержащая ссылку на файл DTD, переформатируется методом reformDoctype, который заменяет URL на имя локального файла. Это делается для того, чтобы гарантировать, что синтаксический анализ файла XML будет проходить независимо от соединения с Интернетом.
Листинг 9.6. Метод grabXML считывает строки файла XML из заданного с помощью URL источника (XMLgrabber.java)
// at this point pw is open to a temp file private void grabXml() throws IOException { URLConnection urlC = theURL.openConnection(); urlC.setUseCaches( false ); urlC.setAllowUserInteraction(false); urlC.connect(); InputStream is = urlC.getInputStream(); InputStreamReader isr = new InputStreamReader(is); BufferedReader br = new BufferedReader( isr ); String tmp = br.readLine() ; while( tmp != null ){ tmp = tmp.trim(); if( tmp.startsWith("<!DOCTYPE") ) { // change to use local copy tmp = reformDoctype( tmp ); } pw.println( tmp ); tmp = br.readLine(); } pw.close(); // does a flush() }
В предположении, что метод ref ormDoctype корректно задает переменную dtdURL, метод grabDtd, показанный в листинге 9.7, загружает DTD в локальный временный файл. В этом листинге также показан служебный метод createURL, который устанавливает значение переменной экземпляра theURL.
Листинг 9.7. Метод grabDtd получает текущее определение moreovernews.dtd (XMLgrabber.java)
// at this point pw is open to a temp file for dtd private void grabDtd() throws IOException { System.out.println("grabDtd:" + dtdURL ); URLConnection urlC = theURL.openConnection(); urlC.setUseCaches( false ); urlC.setAllowUserInteraction(false); urlC.connect(); InputStream is = urlC.getInputStream(); InputStreamReader isr = new InputStreamReader(is); BufferedReader br = new BufferedReader( isr ); String tmp = br.readLine() ; while( tmp != null ){ pw.println( tmp.trim() ); tmp = br.readLine(); } pw.close(); // does a flush() //System.out.println("grabDtd OK"); }
private boolean createURL(String str){ try { theURL = new URL( str ); return true ; }catch(MalformedURLException e){ return false ; } }
Как показано в листинге 9.8, метод reformDoctype извлекает ссылку на файл DTD из строки DOCTYPE, а затем устанавливает значения переменных dtdURL и dtdFname.
Листинг 9.8. Метод reformDocType модифицирует ссылку на DTD (XMLgrabber.java)
// string has doctype declaration, revise to //point to local version private String reformDoctype( String dts ){ int p1 = dts.indexOf( "http:"); // points at h if( p1 < 0 ) return dts ; // int p2 = dts.indexOf( '"', p1 ); int p3 = dts.lastIndexOf('/', p2); if( p3 < 0 ) return dts ; dtdURL = dts.substring( p1 , p2 ); dtdFname = dts.substring( p3 + 1, p2 ); // System.out.println("DTD url:" + dtdURL + "<"); // System.out.println("DTD fname:" + dtdFname + "<"); String tmp = dts.substring(0,p1); // includes " return tmp + dts.substring( p3 + 1 ); }
Так как процесс загрузки в некоторых точках может быть прерван, существует опасность накопления пустых или частично записанных временных файлов. Метод finalize, как показано в листинге 9.9, пытается удалить эти файлы, если они существуют. Если все идет нормально, переменные tfnxml и tfndtd должны содержать null и тогда попытки удалить временные файлы не происходит.
Листинг 9.9. Метод finalize может удалять временные файлы (XMLgrabber.java)
// last chance to clean up temp files if something failed public void finalize(){ if( tfnxml != null ){ new File( saveDir, tfnxml ).delete(); } if( tfndtd != null ){ new File( saveDir, tfndtd ).delete(); } } }