你好,游客 登录
背景:
阅读新闻

使用 Notes 数据库集成 Amazon Web Service

[日期:2009-04-08] 来源:ibm  作者:Ken Yee [字体: ]
这是由三部分组成的系列文章的第 1 部分:本文将介绍有关 Web 服务的知识,并介绍如何在一个 Domino 应用程序中集成 Amazon Web Service,从而查询 Amazon.com 的搜索引擎。

Amazon.com 的老用户可能会记得“Eyes”服务,这个服务可以让您设置自己感兴趣的产品的关键字列表。在出现与您所设置的关键字列表匹配的新产品时,就会向您发送一个电子邮件通知,其中包含这些新产品的链接,以及一些您可以在自己的 Web 站点上使用的服务的介绍,如果您是 Amazon 的会员,这会非常有用。随着使用这项服务的人数不断增加,Amazon.com 决定停止这项服务,因为它对处理器的需求非常高,并且难以维护。

之后,Amazon.com 提供了一项称为 Amazon Web Service 或 AWS 的 Web 服务,您可以使用它在自己的 Web 站点上查询 Amazon 的搜索引擎。这个 Web 服务允许我们创建一个与 Eyes 服务类似的应用程序。您可以将 Amazon Web Service 集成到一个 Notes 应用程序中,从而创建一个比 Eyes 更好的服务;集成方案提供了一种方法,通过这种方法,您可以使用自己的标准对书籍进行分类、标记不相关的书籍,以及在某本书籍停止出版时进行跟踪记录。这种技术目前被用来更新 Notes FAQ Web 站点 上的书籍清单。当然,也可以对 Amazon 上其他类型的产品清单使用这种技术。

本文共有两部分,在第 1 部分中,我们将简要介绍如何在一个 Notes 应用程序中集成 Amazon Web Service(AWS),从而可以使用 Notes Java API 查询 Amazon.com 的搜索引擎。我们还将介绍如何创建一个代理来删除对过期书籍的引用。在本文的第 2 部分中,我们将介绍如何使用 RDBMS 和 J2EE 技术重新创建这个应用程序。本文假设您具有开发 Notes 应用程序的经验,并且具有 Java 编程技巧。您可以从 Amazon.com 的 Web 站点上直接下载 Amazon 的 Web Services SDK

SOAP 和 Web 服务

通过 Web 服务进行通信有很多方法,但是通常都使用以下两种方法:XML/HTTP 或者 SOAP(Simple Object Access Protocol)。XML/HTTP 方法使用了一种针对请求的专门格式化了的 URL。由于 XML/HTTP 访问方法使用一些编码后的参数来发起请求,因此它限制了 URL 的最大长度,这个长度大概是 1,024 个字符(有些服务器可以支持的长度更长,但是 1,024 个字符是一个比较安全的设置)。您所接收到的结果是使用 XML/SOAP 进行编码过的。您可以使用 XSLT 对 XML 进行转换,或者使用 XML 解析器从 XML 读取所需的值。然而,编写 XML 解析器的工作是非常繁琐的。

用来访问 Web 服务的 SOAP 技术利用了 Web 服务提供者所提供的 WSDL(Web Services Description Language)的定义。如果您要发布一个 Web 服务,就需要 WSDL 定义;通常可以通过一个 URL 来使用 Web 服务,这一点在本文后面部分将会看到。在这里,Amazon Web Service 提供了 WSDL 的定义。WSDL 定义描述了如何对请求进行格式化,以及如何读取响应信息,还将告诉您可以使用哪些请求。WSDL 是位于 Web 服务之上的另外一个复杂的层次,但是它真正提供的是生成“封装程序”来访问 Web 服务所需的桥梁,就仿佛这些服务在您自己的机器上一样。

可以使用以友好方式支持 Web 服务的任何语言生成调用 Web 服务的封装程序,这些语言包括 Java、C、VB、PHP、Perl、TCL,等等。另外,在“SOAP 请求”中发送的信息在大小方面存在限制,因为这些信息是在一个 Web 请求体中发送的,而不是在 URL 中发送的;这可以让您发出针对 Web 服务的复杂请求,因为在 Web 请求体中没有大小的限制。

Amazon Web Service

Amazon.com Web Service 页面 中,您可以看到更多有关 Amazon Web 服务的信息。您可以看到 SDK 中提供的 Java、Perl、PHP 和 VB 的示例代码。这个工具中还包括使用 SDK 的文档。这里还有一个开发人员论坛,Amazon 会对其进行监视。在可以调用 Amazon Web 服务的 API 之前,首先需要注册一个开发人员帐号。在 Amazon 的客户使用 Amazon Associate Web 站点来添加 Amazon 的订单记录时,可以使用这个开发人员帐号来提供 Amazon Associate 的推荐记录。如果您使用 Amazon 的 XSLT 服务将 XML 结果转换成 HTML 格式,那么该帐号将自动嵌入所返回的 URL 中,这样您就可以获得推荐费了。

Amazon 在自己的 Web 服务实现中提供了很多功能,这是可以使用 Web 服务来实现哪些功能的一个很好例证。它们不但提供了对搜索引擎的调用功能,而且还提供了管理 Amazon 用户的购物车、购买意向和邮购信息等级的功能。实际上,Web 服务的意图在于允许 Amazon Associate(他们支付推荐费)使用一种更加灵活的方法来构建 Web 店面(Web storefront),从而帮助销售更多的 Amazon 产品。这就是为什么它是一个很好的 Web 服务的例子:它可以帮助 Web 服务的提供者增加收益。

对于本文来说,我们感兴趣的内容是 Amazon 搜索引擎的 Web Services API。在开始之前,我们需要准备以下内容:

  • 安装 Amazon Web Service。有关的更多信息,请参阅 Amazon Web 服务的 SDK 中的文档。
  • 为了进行测试,我们创建一个 Notes 应用程序来使用 AWS。

要使用 Amazon 搜索引擎,您必须在 Notes 应用程序中指定的第一项内容是称为模式的东西;这等同于您在 Amazon.com 主页上可以看到的高级附签:书籍、音乐、工具,等等。然后您就可以根据以下内容进行搜索:关键字、浏览节点(产品目录,例如如果将模式设置为书籍,就可以查找科幻小说)、ASIN、ISBM、UPC、作者、音乐家、演员、导演、发行商、ListMania! 和类似产品(顾客可能购买的其他产品)。对于我们正在构建的 BetterEyes 服务来说,我们将使用关键字进行搜索。


Notes/Domino 6 中对 XML 的支持

Notes/Domino 6 包含了对 XML 的支持,这与 Notes/Domino R5 不同,后者需要添加一个工具包才能支持 XML。我们将来了解一下如何使用 Notes 的 XML 支持来构建 BetterEyes 服务,然后再来了解一下为什么应该使用 SOAP。在 Amazon 的 Web 服务中有一个 XML/HTTP 的例子,请使用 Internet Explorer 5.5 或更高的版本来查看 这个 URL 的内容。

您会看到一个经过很好的格式化的 XML 结果,然后可以单击 + 和 - 符号来展开或收缩层次结构。


图 1. Amazon.com 中的 XML 例子
Amazon.com 中的 XML 例子

使用 Notes/Domino 5 或 Notes/Domino 6,就可以使用 Personal Web Navigator 来查询 Amazon.com 的内容,下面是 Notes LotusScript 引擎中的一段代码:

Dim filename as String
filename$ = "c:\temp\amazon.xml"
Dim NURLdb As New NotesDatabase("","") 
' To reference the Internet browser database
Dim Nwebdoc As NotesDocument
If NURLdb.OpenURLDb Then
    Dim filename As String    
    ' download the XML encoded WS reply    
    If (False) Then    
    theURL = "http://xml.amazon.com/onca/xml?v=2.0&dev-t=webservices-20
&t=thelotusnotesfaqA&KeywordSearch=Lotus+Notes&mode=books
&type=lite&page=1&f=xml"   
    	Set Nwebdoc=NURLdb.getdocumentbyurl(theUrl, True, False)    
    	' get XML from attachment    
    	Dim Item As NotesItem    
    	Dim Object As NotesEmbeddedObject    
    	Set Item = Nwebdoc.GetFirstItem("$FILE")    
    	Dim ItemName As String    
    	ItemName = Item.Values(0)    
    	Set Object = Nwebdoc.GetAttachment(ItemName)    
    	Call Object.ExtractFile(filename$)    
    	End If
    
End If

当 Amazon 发回 XML 响应时,将其作为一个附件保存在 Personal Web Navigator 数据库中。需要将附件的内容保存到文件系统中,这样就可以从响应中处理 XML 的内容了。Notes/Domino 6 有两个内嵌的 XML 解析器:SAX 和 DOM。SAX 解析器使用的内存更少,因为它是一个回调风格的 API,因此它不需要将整个 XML 文档都保存在内存中。DOM 解析器更易于使用,因此我们将着重使用 DOM。有关 SAX 和 DOM 解析器的更多内容,请参阅 LDD Today 撰写的文章“LotusScript: XML classes in Notes/Domino 6”。

下面的代码将输出从 Amazon Web 服务查询中返回的 XML 结果:

    ' open XML file as a stream    
    Dim xml_in As NotesStream    
    Set xml_in=session.CreateStream    
    If xml_in.Open(filename$) Then    
    	If xml_in.Bytes = 0 Then    
       		Messagebox filename$ + " is empty"
    	End If
    
    	' create a temporary output file because it's not a pipelined XML operation
		Dim xml_out As NotesStream
		Dim outfilename as String
		outfilename$ = "c:\temp\amazon.tmp" ' create output file
		Set xml_out=session.CreateStream
		If Not xml_out.Open(outfilename$) Then
			Messagebox "Cannot create " & outfilename$,, "TXT file error"
			Exit Sub
		End If
		xml_out.Truncate
		' parse using DOM parser
		Set domParser=session.CreateDOMParser(xml_in, xml_out)
		On Error Goto DumpDOMLog
		DOMParser.ExitOnFirstFatalError = True
		DOMParser.InputValidationOption = 0 
		' disable validation of problematic Amazon XML results
		domParser.Process
		On Error Goto 0
		Dim docNode As NotesDOMDocumentNode
		Set docNode = domParser.Document
		' last child at root is ProductInfo
		Dim productinfoNode As NotesDOMNode
		Set productinfoNode = docNode.LastChild
		' iterate through all the Details children
		Dim detailsNode As NotesDOMNode
		Set detailsNode = productinfoNode.FirstChild
		While (Not detailsNode.IsNull)
			Dim productname As String
			Dim publisher As String
			productname = ""
			Dim infoNode As NotesDOMNode
			Set infoNode = detailsNode.FirstChild
			While (Not infoNode.IsNull)
				If (infoNode.NodeName = "ProductName") Then
					productname = infoNode.FirstChild.NodeValue
				Elseif (infoNode.NodeName = "Manufacturer") Then
					publisher = infoNode.FirstChild.NodeValue
				End If
				Set infoNode = infoNode.NextSibling
			Wend
			If (productname <> "") Then
				Messagebox "Found " + productname 
				+ " from " + publisher
			End If
			Set detailsNode = detailsNode.NextSibling
		Wend
	End If
	Exit Sub
DumpDOMLog:
	Msgbox "DOMParser error: " & DOMParser.log
	Exit Sub    

正如您可以看到的,这段代码非常复杂,它创建了一些代码来遍历 XML 文档,从而检索所需要的信息。XML 输入验证被关闭,因为 Amazon 的 XML 响应存在问题,在 DOMParser 的错误记录中,该问题表现为一个无效的 URL;关闭这个有效性验证在处理很多 XML 时是必需的,因为可能会错误地生成 XML。另外,还需要对 XML 模式有足够的了解,知道自己希望了解哪些级别的信息。在上面这个例子中,作者信息位于一个级别较低的节点上。您还需要了解这些节点是如何命名的,并且需要正确拼写它们(如果拼写错误,就不能进行什么错误检查了)。

在下一节中,我们将介绍 SOAP 封装程序是如何对此进行简化,并以更加直观和更加安全的方式表现它们。

Axis SOAP 封装程序

正如前面所介绍的,大部分语言都有一个 SOAP 封装程序生成器。这些生成器使用一个 WSDL 描述并生成一个封装对象,这样就可以使用 Web 服务,就像它是语言的一部分那样。单击这里 查看 Amazon 的 Web Service WSDL 描述的内容。这是最新版本的 Amazon Web Service WSDL 描述。您可以将其保存为一个文本文件 AmazonWebServices.wsdl。

在 Java 中,实际的 SOAP 封装程序来自 Apache Web 服务器项目。该项目的名称为 Axis,您可以从 Apache Axis Web 站点 上下载它。Axis 在很多工具中以各种形式得到了广泛的应用,这些工具中包括 IBM 的 Web Services Toolkit 和 Borland 的 JBuilder。尽管我们只是要使用它来生成一个 SOAP 封装程序与一个 Web 服务进行通信,但是这也需要生成封装程序,这样您就可以作为 Web 服务来提供一些 Java 类了。

在下载 Axis 之后,请按照 Web 站点上的提示来安装它。完成安装 Axis 之后,就可以使用下面以下命令行告诉 Axis 为 Amazon Web 服务生成封装类了:

java java org.apache.axis.wsdl.WSDL2Java AmazonWebServices.wsdl
 --output build/axis --verbose --package com.amazon.soap.axis

这个命令将生成一些 Java 文件,它们将编译到 com.amazon.soap.axis Java 包中。下面是生成 ProductInfo XML 节点使用的代码:

/**
 * ProductInfo.java
 *
 * This file was auto-generated from WSDL
 * by the Apache Axis WSDL2Java emitter.
 */
package com.amazon.soap.axis;
public class ProductInfo implements java.io.Serializable {
    private java.lang.String totalResults;    
    private java.lang.String listName;    
    private com.amazon.soap.axis.Details[] details;
		
    public ProductInfo() {    
    }
    public java.lang.String getTotalResults() {    
    return totalResults;    
    }
		
    public void setTotalResults(java.lang.String totalResults) {    
    this.totalResults = totalResults;    
    }
    public java.lang.String getListName() {    
    return listName;    
    }
		
    public void setListName(java.lang.String listName) {    
    this.listName = listName;    
    }
		
    public com.amazon.soap.axis.Details[] getDetails() {    
    return details;    
    }
		
    public void setDetails(com.amazon.soap.axis.Details[] details) {    
    this.details = details;    
    }
		
    private java.lang.Object __equalsCalc = null;    
    public synchronized boolean equals(java.lang.Object obj) {    
    if (!(obj instanceof ProductInfo)) return false;    
    ProductInfo other = (ProductInfo) obj;    
    if (obj == null) return false;    
    if (this == obj) return true;    
    if (__equalsCalc != null) {    
        return (__equalsCalc == obj);
    }    
    __equalsCalc = obj;    
    boolean _equals;    
    _equals = true &&     
        ((totalResults==null && 
        other.getTotalResults()==null) ||        
        (totalResults!=null &&        
        totalResults.equals(other.getTotalResults()))) &&        
        ((listName==null && other.getListName()==null) ||        
        (listName!=null &&        
        listName.equals(other.getListName()))) &&        
        ((details==null && other.getDetails()==null) ||         
        (details!=null &&        
        java.util.Arrays.equals(details, other.getDetails())));
    __equalsCalc = null;    
    return _equals;    
    }
		
    private boolean __hashCodeCalc = false;    
    public synchronized int hashCode() {    
    if (__hashCodeCalc) {    
        return 0;
    }    
    __hashCodeCalc = true;    
    int _hashCode = 1;    
    if (getTotalResults() != null) {    
        _hashCode += getTotalResults().hashCode();
    }    
    if (getListName() != null) {    
        _hashCode += getListName().hashCode();
    }    
    if (getDetails() != null) {    
        for (int i=0;        
            i<java.lang.reflect.Array.getLength(getDetails());            
            i++) {            
            java.lang.Object obj 
            = java.lang.reflect.Array.get(getDetails(), i);            
            if (obj != null &&            
            !obj.getClass().isArray()) {            
            _hashCode += obj.hashCode();            
            }
        }
    }    
    __hashCodeCalc = false;    
    return _hashCode;    
    }
		
    // Type metadata    
    private static org.apache.axis.description.TypeDesc typeDesc =    
    new org.apache.axis.description.TypeDesc(ProductInfo.class);
		
    static {    
    org.apache.axis.description.FieldDesc field 
    = new org.apache.axis.description.ElementDesc();    
    field.setFieldName("totalResults");    
    field.setXmlName(new javax.xml.namespace.QName("", "TotalResults"));    
    field.setXmlType(new javax.xml.namespace.QName
    ("    http://www.w3.org/2001/XMLSchema    ", "string"));    
    typeDesc.addFieldDesc(field);    
    field = new org.apache.axis.description.ElementDesc();    
    field.setFieldName("listName");    
    field.setXmlName(new javax.xml.namespace.QName("", "ListName"));    
    field.setXmlType(new javax.xml.namespace.QName
    ("    http://www.w3.org/2001/XMLSchema    ", "string"));    
    typeDesc.addFieldDesc(field);    
    field = new org.apache.axis.description.ElementDesc();    
    field.setFieldName("details");    
    field.setXmlName(new javax.xml.namespace.QName("", "Details"));    
    field.setXmlType(new javax.xml.namespace.QName
    ("urn:PI/DevCentral/SoapService", "DetailsArray"));    
    typeDesc.addFieldDesc(field);    
    };
		
    /**
	* Return type metadata object
    */    
    public static org.apache.axis.description.TypeDesc getTypeDesc() {    
    return typeDesc;    
    }
		
    /**    
    * Get Custom Serializer    
    */    
    public static org.apache.axis.encoding.Serializer getSerializer(    
    java.lang.String mechType,    
    java.lang.Class _javaType,    
    javax.xml.namespace.QName _xmlType) {    
    return     
    new org.apache.axis.encoding.ser.BeanSerializer(    
    _javaType, _xmlType, typeDesc);    
    }
		
    /**    
    * Get Custom Deserializer    
    */    
    public static org.apache.axis.encoding.Deserializer getDeserializer(    
    java.lang.String mechType,     
    java.lang.Class _javaType,     
    javax.xml.namespace.QName _xmlType) {    
    return     
    new org.apache.axis.encoding.ser.BeanDeserializer(    
    _javaType, _xmlType, typeDesc);    
    }
    
}

这段代码看起来非常复杂,但是它的基本功能是允许简单地访问 XML 的结果,实现这一点的方法是将 XML ProductInfo 节点的属性当作 Java String 对象看待,将下面的 XML 节点当作 Java 数组对象看待。封装程序还要使这些对象“连续”,这样您就可以将 XML 节点通过网络转换成一个 Java 程序,或者将 XML 节点保存到文件中。

在生成封装类之后,您就可以创建一个简单的程序来调用 Amazon Web Service。将以下代码保存到 AmazonUpdate.java 文件中:

import com.amazon.soap.axis.*;
public class AmazonUpdate
{
    public static void main(String[] args) throws Exception    
    {    
    	AmazonSearchService service = new AmazonSearchServiceLocator();    
   		AmazonSearchPort port = service.getAmazonSearchPort();    
    	KeywordRequest request = new KeywordRequest();
		
    	request.setKeyword(java.net.URLEncoder.encode("Lotus Notes"));    
    	request.setMode("books");    
    	request.setTag("");    
    	request.setType("lite");    
    	request.setDevtag("");    
    	request.setPage("1");
		
    	ProductInfo result = null;    
    	try {    
        	result = port.keywordSearchRequest(request);
    	} catch (Exception e) {    
        	e.printStackTrace();
    	}    
    	Details[] details = result.getDetails();    
    	for (int i = 0; i < details.length; i++) {    
       		// read results out        
        	String resultTitle = details[i].getProductName();        
        	System.out.println("Title #" + i + " is " + resultTitle);
    	}
		
    }
}

现在您可以使用下面的命令来编译这个程序和 SOAP 封装程序了:

javac AmazonUpdate.java build/axis/com/amazon/soap/axis/*.java

使用下面的命令来运行这个程序:

java -classpath build/axis/com/amazon/soap/axis;%CLASSPATH% AmazonUpdate

它会输出第一个结果页面中的书籍的标题。注意,您并不需要了解太多有关 Amazon Web 服务的内容,甚至不需要指定 URL 来访问 Amazon Web Service,因为这些信息都内嵌在 WSDL 文件中的。您还要进行 Java 的类型/方法检查,确保没有错误地调用 Amazon Web Services API,也没有使用错误的数据类型对结果进行解释。


AmazonUpdate Java 程序

现在我们已经有一个可以调用 Amazon Web 服务的基本框架了,接下来就可以开发所需要的完整应用程序了。不幸的是,在 Amazon 的 Web Services API 中有一个 bug:所返回的结果总数并不正确。每个 Web 服务调用最多可以返回 10 个结果。我们需要使用一个循环进行遍历,直到所返回的结果少于 10 个或出现错误为止。循环体如下所示:

boolean fKeepgoing = true;
int realcount = 0;
int page = 0;
do {
    page++;    
    request.setPage("" + page);
		
    ProductInfo result = null;    
    try {    
    result = port.keywordSearchRequest(request);    
    } catch (Exception e) {    
    	org.apache.axis.AxisFault afe = null;    
    	if (e instanceof org.apache.axis.AxisFault) {    
        	afe = (org.apache.axis.AxisFault) e;
    	}    
    	if ((afe == null) || (afe.getFaultString().indexOf("Bad Request") < 0)) {    
        	// Amazon's results "end" when the page you request gets a request 
        	error or if less than 10 results are returned        
        	e.printStackTrace();
    	}    
    	fKeepgoing = false;    
    	break;    
    }
		
    Details[] details = result.getDetails();    
    for (int i = 0; i < details.length; i++) {    
    	// values that will be passed to tracker    
    	String resultTitle = null;    
    	String resultISBN = null;    
    	String resultASIN = null;    
    	String resultAuthors = null;    
    	String resultPublisher = null;
		
    	// read results out    
    	resultTitle = details[i].getProductName();    
    	resultASIN = details[i].getAsin();    
    	if (details[i].getIsbn() != null) {    
        	resultISBN = details[i].getIsbn();
    	}    
    	String authors[] = details[i].getAuthors();    
    	String authorlist = "";    
    	if (authors != null) {    
        	for (int j = 0; j < authors.length; j++) {        
        	if (j > 0) {        
            	authorlist += ", ";
        	}        
        	authorlist += authors[j];        
        	}
    	}    
    	resultAuthors = authorlist;    
    	if (details[i].getManufacturer() != null) {    
        	resultPublisher = details[i].getManufacturer();
    	}
		
    	// give results to tracker    
    	tracker.updateBook(    
        	resultTitle,        
        	resultASIN,        
        	resultISBN,        
        	resultAuthors,        
        	resultPublisher);
		// increment result counter    
    	realcount++;
		
    }
		
    if (details.length < 10) {    
    	fKeepgoing = false;    
    }
} while (fKeepgoing);

BookUpdate 接口

您可能会纳闷 tracker.updateBook() 调用到底是用来干什么用的。它是一个 Java 接口调用,使用它可以更新我们要跟踪的书籍的数据库。通过将其声明为一个接口,可以使用不同的类来获取这些信息,这样就可以对应用程序进行调试,或者将信息写入一个 Notes 数据库、关系型数据库或其他数据存储器中。

public interface BookTracker
{
    public void updateBook(String title, String ASIN, String ISBN, 
    String authors, String publisher, String rating, int numreviews) 
    throws Exception;
}

调试初步:将调试信息输出到标准输出设备上

正如您猜想的那样,调试类非常简单,因为它所做的工作不过是向标准输出设备输出一些信息,这些信息就是运行 Java 应用程序时在命令窗口中显示的信息。这个类如下所示:

public class DumpBookTracker implements BookTracker {
    // update book from amazon query    
    public void updateBook(    
    	String title,    
    	String ASIN,    
    	String ISBN,    
    	String authors,    
    	String publisher) {    
    	System.out.println(title);    
    	System.out.println("; ASIN: " + ASIN);    
    	if (ISBN != null) {    
    	System.out.println("; ISBN: " + ISBN);    
    }    
    	if (publisher != null) {    
    	System.out.println("; Publisher: " + publisher);    
    }    
    	if (authors != null) {    
    	System.out.println("; Authors: " + authors);    
    	}    
    }
}

与 Notes 进行集成:表单和视图

现在就可以创建一个 Notes 表单来显示这些信息了:


图 2. Notes 表单
Notes 表单

前 5 行的内容分别是标题、作者、出版商、ISBN 和 ASIN,其中包含保存在 BookTracker 接口中的信息。State 字段允许我们将这本书标记成已经不再出版或不再具有相关性(例如,突然发现有一本书是与 Notes/Domino 无关的)。Notes Version 字段可以判断这本书所使用的 Notes 的版本。Category 字段可以确定该书属于哪一类:管理类的、编程类的,等等。

您还需要两个 Notes 视图。一个视图名为 LUBookASIN。第一个(也是惟一一个)排序列中包含 ASIN 字段。ASIN 是 Amazon 为每种产品分配唯一 ID。对于电子书籍来说,ISBM 可以为空。隐藏的 lookup 视图可以用来更新书籍的状态,这样我们就可以知道一本书何时最后一次出现在 Amazon 的 Web 站点上;如果某一本书不再能通过关键字进行查询,那么将无法发布这本书,因为在 Amazon 的搜索引擎中,不会返回不再出版的书籍。如果可以使用一个关键字搜索到某一本书籍,但通过查阅数据块中的 ASIN 却无法查到这本书,那么这本书是一本新书,需要将它添加到数据库中。

第二个视图称为 Books\by Status。这个视图中的第一个分类列是 Status 字段。第二列是 Notes Version 字段。第三列是 Title 字段。您可以使用这个视图来管理 AmazonUpdate 程序可以添加的书籍。

将新书保存到 Notes 数据库中

现在我们有地方来存放 Java 应用程序找到的每本书了,接下来可以编写一个 BookTracker 类,该类知道如何将这些书籍保存到 Notes 数据库中。它比那个调试类稍微复杂一些,但也不是很复杂。它使用 Notes Java API,因此您需要在自己的 classpath 中包含 notes.jar 文件(在 notes.exe 所在的 Notes 目录中):

import lotus.domino.*;
public class NotesBookTracker implements BookTracker {
    lotus.domino.Session s;    
    Database db;    
    View booksview;
		
    // constructor    
    	public NotesBookTracker(String server, String dbname) 
		NotesException {    
    	// initialize Notes access    
    	NotesThread.sinitThread();    
    	s = NotesFactory.createSession();
		
    	// get book ISBN view    
    	db = s.getDatabase(server, dbname);    
    	booksview = db.getView("(LUBookASIN)");
		
    }
		
    // Java pseudo-destructor    
    public void finalize() throws NotesException {    
    // clean up Notes    
    NotesThread.stermThread();    
    }
		
    // update book from amazon query    
    public void updateBook(    
    	String title,    
    	String ASIN,    
    	String ISBN,    
    	String authors,    
    	String publisher)    
    	throws NotesException {    
    	// check for blank ISBN    
    	if (ISBN == null) {    
    	System.out.println(title + " has a null ISBN!");    
    }    
    	// check for blank ASIN    
    	if (ASIN == null) {    
    	System.out.println(title + " has a null ASIN!");    
    }
		
    Document doc;    
    // see if we can find the book    
    doc = booksview.getDocumentByKey(ASIN, true);    
    if (doc == null) {    
    // new book, so we have to add it to the database    
    doc = db.createDocument();    
    doc.replaceItemValue("Form", "Book");    
    doc.replaceItemValue("Title", title);    
    doc.replaceItemValue("ASIN", ASIN);    
    if (ISBN != null) { // E-books don't have an ISBN    
    doc.replaceItemValue("ISBN", ISBN);    
    }    
    if (authors != null) {    
    doc.replaceItemValue("Authors", authors);    
    }    
    if (publisher != null) {    
    doc.replaceItemValue("Publisher", publisher);    
    }    
    doc.replaceItemValue("State", "Added");    
    }// tag it w/ timestamp so we know when it was last found    
    // so we can expire unfound books in a scheduled agent    
    DateTime timenow = s.createDateTime("Today");    
    timenow.setNow();    
    doc.replaceItemValue("LastFound", timenow);    
    // save document    
    doc.save();    
    }
}

编辑表单并查看书籍

在从 Amazon.com 上接收到信息之后,接下来需要在 Notes 数据库中编辑 State、Notes Version 和 Categories 字段的内容。下图显示了一个处于编辑模式的文档。


图 3. Notes 文档
Notes 文档

在编辑了 Java 代理所发现的新书的字段之后,就应该有一个非常有用的视图了,如下所示:


图 4. Book 视图
Book 视图

将书籍标记为不再出版

这是我们的 BetterEyes 应用程序比 Amazon 的 Eyes 服务多做的一件事。当有些书籍不再出版时,Eyes 服务并不会通知您。Amazon 为 Amazon Associates 提供了另外一个特性,它可以让您了解 Web 站点的访问者何时引用了那些已经不再可用产品的链接;然后您应该手工编辑这些链接。

在 Notes 中,可以创建一个调度代理,将一些书籍标记为不再出版。您会注意到,每次运行 AmazonUpdate 应用程序时,都会对现有的文档进行更新,并存储一个 LastFound 日期。然后,我们可以创建一个调度代理,它每 30 天运行一次以下文档:


图 5. 调度代理
调度代理

并执行这个简单操作:


图 6. 调度代理:简单操作
调度代理:简单操作

以上就是这个代理要执行的所有操作。我们现在可以创建一个视图来显示目前可以订购的书籍,这可以通过检查 State 字段的值并确定它不是 No Longer Published 来实现。


用作 Notes/Domino 6 Java 代理的 AmazonUpdate

将 AmazonUpdate Java 应用程序转换为一个 Notes/Domino 6 Java 代理非常简单。但不能在 R5 中这样做,因为 R5 使用的是 Java Runtime 1.1.8,而 Notes/Domino 6 使用的是 JRE 1.3.1。您需要做的第一件事情就是将这些 JAR 文件添加到 Notes 和 Domino 的类路径中。

在 Notes/Domino 6 和 Notes/Domino R5 中,对 Java 类路径的处理有所不同,因为 Notes/Domino 6 使用的是更加新的 JVM。在 Notes 或 Domino 目录中,有一个 JVM 子目录,其中保存了 JVM 二进制文件和 JAR 文件。如果您希望将 JAR 文件添加到代理的类路径中,那么可以将其添加到 jvm\lib\ext 目录中。您应该将 axis.jar、jaxrpc.jar、saaj.jar、commons-logging.jar、commons-discovery.jar 添加到这个目录中(请参阅 Axis 文档,了解到底需要哪些文件,因为在发布时, JAR 文件可能会发生变化)。

AmazonUpdate.jar 文件也应该被添加到 jvm\lib\ext 目录中。JAR 文件中包括我们为这个应用程序创建的所有 CLASS 文件和 SOAP 代理类。通过转至存放应用程序的目录(上面假设在 c:\AmazonUpdate 目录)中并输入下面的命令,可以创建这个 JAR 文件:

jar -cvf AmazonUpdate.jar *.class com\amazon\soap\axis\*.class
cd build\axis
jar -cvf ..\..\AmazonUpdate.jar com\amazon\soap\axis\*.class

我们之所以这样做,而不是将所有的 JAVA 文件都导入 IDE,是因为如果您试图在 Domino Designer 中导入很多 JAVA 文件,那么 Domino Designer 的速度就会明显减慢,因为它试图确定要展开/收缩显示哪些代码。将不会发生变化的代码放入一个 JAR 文件,并将其放到类路径中,这样会加快速度。

另外,您还需要在路径中添加一个 SAX API 兼容的 XML 解析器。最简单的 XML 解析器就是 Apache 的 Crimson 解析器,它来自 crimson.jar 中的一个表单。不幸的是,不能将其添加到 jvm\lib\ext 目录中,因为 Notes/Domino 6 在 Notes 可执行目录的 xml4j.jar 中有自己的 DOM 解析器。幸运的是,我们可以通过使用 JavaUserClasses Notes.ini 变量,将自己的类放到包含 xml4j.jar 文件的搜索路径的前面。请将下面这行代码添加到 Notes.ini 文件中(假设这个文件位于 C:\JARs 目录中):

JavaUserClasses=c:\JARs\crimson.jar

注意您在 JavaUserClasses 变量中放入了多少路径。对于所有 Notes.ini 变量而言,每个变量的最大长度都是 255 个字符。这个限制很容易被突破,因为所有有用的 Java JAR 文件都可以在此列出。惟一的解决方案就是将它们放在位于某个驱动的根目录上的只有一个字符的目录名中,但是,如果不断添加 JAR 文件,那么这种方法也不能解决多少问题。

在完成这种设置之后,就可以在 Domino Designer 中创建一个 Java 代理了。您所需要的只是以下几行代码:

import lotus.domino.*;
public class JavaAgent extends AgentBase {
    public void NotesMail() {
		
    	try {    
        	Session session = getSession();        
        	AgentContext agentContext = session.getAgentContext();
        	BookTracker tracker = new DumpBookTracker();
    	//BookTracker tracker = new NotesBookTracker("SlipGate","Notes")    
       		AmazonUpdate.getAmazonBooks("lotus domino", tracker);        
        	AmazonUpdate.getAmazonBooks("lotus notes", tracker);
	    } catch(Exception e) {    
        	e.printStackTrace();
    	}
		
    }
}

我们再次使用 DumpBookTracker API 进行测试。将这个代理设置为手工从菜单中运行,这样就可以在 Notes 客户机中进行测试了:


图 7. Agent Properties 对话框
Agent Properties 对话框

在运行这个代理之前,请在 Notes 客户端中选择 File - Tools - ShowJavaConsole,这样就可以看到在 BookTracker 类中调用 System.out.println 时的所有输出。来自 Java 虚拟机中的错误消息,包括有关不能找到某些类的错误消息,都会显示在这个窗口中。


结束语

我们已经创建了一个使用 Amazon Web Service 的非常有用的 Java 应用程序。开源的 Axis SOAP 代理生成器将这个任务变得太过简单,因为它看起来就像是我们正在使用一些普通的 Java 类;只要这个 Web 服务提供一个 WSDL 文件,就无需要担心如何访问这个 Web 服务,也无需要担心如何与之进行交谈。

通过将来自 Amazon Web Service 的信息保存到一个 Note 数据库中,可以构建一个比 Amazon 以前的 Eyes 服务更有用的程序,因为我们既可以添加新书,也可以跟踪那些已经不再出版的书籍。此外,可以将这个 Notes 数据库放到 Domino 服务器上,通过 Web 对其进行访问。所有的 Notes 工作都可以在几个小时内完成,而且为 Amazon 通过 Amazon Web 服务提供的许多有用信息提供了一个接口。

在下一篇文章中,我们将使用 DB2 和 JSP(JavaServer Page)来重新构建一个类似的应用程序。然后,我们将使用 EJB 和一些自动化工具(MiddleGen、XDoclet 和 Ant)来重新构建这个应用程序,使用这些工具,可以降低 EJB 使用的繁琐程度。



参考资料



关于作者

   Ken Yee 从一开始就是一名咨询顾问和 Lotus 的业务合作伙伴。他从 1989 年开始从事软件开发,并对 J2EE/Domino 集成项目感兴趣。他的公司 KEY Enterprise Solutions 进行过 Notes、Domino、IIS/ASP、Java、ActiveX/COM 和 C++ 开发,完成了 Lotus、Inso/Stellent、Logica、eVelocity、World Bank 和 Analysis Group 的管理项目。KEY Enterprise Solutions 公司为 Notes 社区维护着 Notes/Domino FAQ(这是网络上的第一个 Notes FAQ),为 Java 社区维护着 Java Servlet FAQ。

推荐 打印 | 录入: | 阅读:
相关新闻      
本文评论   
评论声明
  • 尊重网上道德,遵守中华人民共和国的各项有关法律法规
  • 承担一切因您的行为而直接或间接导致的民事或刑事法律责任
  • 本站管理人员有权保留或删除其管辖留言中的任意内容
  • 本站有权在网站内转载或引用您的评论
  • 参与本评论即表明您已经阅读并接受上述条款