1. 程式人生 > >AWS雲搜尋的使用:極簡Java API

AWS雲搜尋的使用:極簡Java API

當前,許多應用重度依賴於搜尋功能。從電子商務網站中尋找合適的產品,到社交網路中搜索尋人,再到地圖網站中尋找POI和地址,依賴於搜尋的應用非常廣泛。

亞馬遜新推出的雲搜尋服務,為自行實現搜尋功能或定製安裝Apache LuceneApache Solrelasticsearch等流行產品提供了可行的替代方式。他們這樣描述該服務:

“它是一個完全託管的雲搜尋服務,該服務允許使用者十分方便地在應用中整合快速且高度可擴充套件的搜尋功能。【它】讓使用者擺脫了運營和擴充套件搜尋平臺的負擔。使用者不用再去關心硬體配置、資料分割槽和軟體補丁的問題了。”

這個實現方式中,我們發現的唯一缺點是缺乏用於上傳資料和實現搜尋的Java API。雖然亞馬遜提供的REST API也能達到上述的目的,但是在Java程式碼中使用它們並不方便。為了簡化搜尋呼叫和資料上傳功能,我們開發了一些簡單的Java API,將在本文中逐一介紹。

資料定義

儘管亞馬遜提供了資料上傳和搜尋響應的資料定義(以XMLJSON兩種方式),但資料上傳的文件中僅定義了Relax NG模式,而搜尋響應則未定義任何模式。

在我們的實現方式中,我們決定使用XML資料格式而不是JSON,這是因為進行XML資料封裝更加簡單——XML使用規範的資料格式,而JSON則是動態的(JSON的標籤是動態定義,每個請求各異)。我們分別用下邊的兩種模式(列表1和列表2)來上傳資料和搜尋結果。

<?xml version="1.0" encoding="UTF-8"?>
<xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema"  elementFormDefault="qualified">
         ………………………………………………………………………………………….
	 <xsd:complexType name="fieldType">
	         <xsd:simpleContent>
	                 <xsd:extension base="xsd:string">
 	                           <xsd:attribute name="name" type="field_nameType" />
	                 </xsd:extension>
	         </xsd:simpleContent>
	</xsd:complexType>

        <xsd:complexType name="addType">
	         <xsd:sequence>
	                 <xsd:element name="field" type="fieldType" maxOccurs="unbounded" />
	         </xsd:sequence>
	         <xsd:attribute name="id" type="IDType" />
	         <xsd:attribute name="version" type="versionType" />
	         <xsd:attribute name="lang" type="xsd:language" />
        </xsd:complexType>

	<xsd:complexType name="deleteType">
	         <xsd:attribute name="id" type="IDType" />
	         <xsd:attribute name="version" type="versionType" />
	</xsd:complexType>

	<xsd:complexType name="batchType">
	         <xsd:sequence>
	                 <xsd:element name="add" type="addType" minOccurs="0" maxOccurs="unbounded" />
	                 <xsd:element name="delete" type="deleteType" minOccurs="0" maxOccurs="unbounded" />
	         </xsd:sequence>
	</xsd:complexType>

	<xsd:element name="batch" type="batchType" />

	<xsd:simpleType name="statusType">
	         <xsd:restriction base="xsd:string">
	                 <xsd:enumeration value="success"/>
	                 <xsd:enumeration value="error" />
	         </xsd:restriction>
        </xsd:simpleType>

	<xsd:complexType name="errorsType">
	         <xsd:sequence>
	                 <xsd:element name="error" type="xsd:string" maxOccurs="unbounded" />
	         </xsd:sequence>
        </xsd:complexType>

        <xsd:complexType name="warningsType">
	         <xsd:sequence>
	                 <xsd:element name="warning" type="xsd:string" maxOccurs="unbounded" />
	         </xsd:sequence>
        </xsd:complexType>
	    
	<xsd:complexType name="responseType">
	         <xsd:sequence>
                        <xsd:element name="errors" type="errorsType" minOccurs="0" />
	                <xsd:element name="warnings" type="warningsType" minOccurs="0" />
	         </xsd:sequence>
                 <xsd:attribute name="status" type="statusType"/>
	         <xsd:attribute name="adds" type="xsd:int"/>
	         <xsd:attribute name="deletes" type="xsd:int"/>
	</xsd:complexType>   

        <xsd:element name="response" type="responseType" />
</xsd:schema> 
    

Listing 1 Upload data schema

<xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema"
	targetNamespace="http://cloudsearch.amazonaws.com/2011-02-01/results"
	xmlns="http://cloudsearch.amazonaws.com/2011-02-01/results"
	elementFormDefault="qualified">

	<xsd:complexType name="constraintType">
	         <xsd:attribute name="value" type="xsd:string"/>
	         <xsd:attribute name="count" type="xsd:int"/>
        </xsd:complexType>

        <xsd:complexType name="facetType">
	         <xsd:sequence>
                          <xsd:element name="constraint" type="constraintType" maxOccurs="unbounded"/>
	         </xsd:sequence>
  	         <xsd:attribute name="name" type="xsd:string" />
        </xsd:complexType> 
        <xsd:complexType name="facetsType">
	         <xsd:sequence>
 	                  <xsd:element name="facet" type="facetType" maxOccurs="unbounded"/>
	         </xsd:sequence>
        </xsd:complexType>

        <xsd:complexType name="infoType">
	         <xsd:attribute name="rid" type="xsd:string" />
	         <xsd:attribute name="time-ms" type="xsd:int" />
                 <xsd:attribute name="cpu-time-ms" type="xsd:int" />
        </xsd:complexType>

        <xsd:complexType name="dType">  
	         <xsd:simpleContent>
                         <xsd:extension base="xsd:string">
	                           <xsd:attribute name="name" type="xsd:string" />
	                 </xsd:extension>
  	         </xsd:simpleContent>
  	</xsd:complexType>  

        <xsd:complexType name="hitType">
	         <xsd:sequence>
                           <xsd:element name="d" type="dType" maxOccurs="unbounded"/>
	         </xsd:sequence>
  	         <xsd:attribute name="id" type="xsd:string" />
        </xsd:complexType>

        <xsd:complexType name="hitsType">
	         <xsd:sequence>
  	                  <xsd:element name="hit" type="hitType" maxOccurs="unbounded"/>
                 </xsd:sequence>
  	         <xsd:attribute name="found" type="xsd:int" />
	         <xsd:attribute name="start" type="xsd:int" />
         </xsd:complexType>

        <xsd:complexType name="resultsType">
  	         <xsd:sequence>
  	                  <xsd:element name="rank" type="xsd:string" />
	                  <xsd:element name="match-expr" type="xsd:string" />
  	                  <xsd:element name="hits" type="hitsType" minOccurs="0"/>
  	                  <xsd:element name="facets" type="facetsType" minOccurs="0"/>
                          <xsd:element name="info" type="infoType" />
                  </xsd:sequence>
        </xsd:complexType>
 
        <xsd:element name="results" type="resultsType"/>

        <xsd:complexType name="messageType">
  	         <xsd:attribute name="severity" type="xsd:string" />
  	         <xsd:attribute name="code" type="xsd:string" />
  	         <xsd:attribute name="message" type="xsd:string"/>
         </xsd:complexType>  

        <xsd:complexType name="errorType">
  	         <xsd:sequence>
  	                  <xsd:element name="error" type="xsd:string" />
  	                  <xsd:element name="rid" type="xsd:string" />
  	                  <xsd:element name="time-ms" type="xsd:int" />
  	                  <xsd:element name="cpu-time-ms" type="xsd:int" />
	                  <xsd:element name="messages" type="messageType" maxOccurs="unbounded" />
                </xsd:sequence>
       </xsd:complexType>

       <xsd:element name="error" type="errorType" />

</xsd:schema> 

Listing 2 Search results data schema


查詢定義

除了資料定義,實現搜尋API還需要查詢定義。我們已經建立了一組類,用來實現亞馬遜的查詢定義

這個搜尋查詢的核心是過濾器。我們引入了SearchQueryFilter 介面,並提供了兩種實現方式——Search Query Value Filter(列表3)和Search Query Filter Operation(列表4)。

public class SearchQueryValueFilter implements SearchQueryFilter{

    private String _field;
    private String _value;
    private boolean _isExclude;
    private boolean _isNumeric;
	  
    public SearchQueryValueFilter(){}

    public SearchQueryValueFilter(String field, String value, boolean isNumeric, boolean isExclude){
        _field = field;
        _value = value;
        _isExclude = isExclude;
	_isNumeric = isNumeric;
    }

	    public String getField() {
	        return _field;
	    }

	    public void setField(String field) {
	        _field = field;
	    }

	    public String getValue() {
	        return _value;
	    }

	    public void setValue(String value) {
	        _value = value;
	    }

	    public boolean isExclude() {
	        return _isExclude;
	    }

	    public void setExclude(boolean isExclude) {
	        _isExclude = isExclude;
	    }

	    public boolean isNumeric() {
	        return _isNumeric;
	    }

	    public void setNumeric(boolean isNumeric) {
	        _isNumeric = isNumeric;
	    }

	    @Override
	    public String toString(){
	        StringBuffer sb = new StringBuffer();
	        if(_isExclude){
	            sb.append("(not ");
	        }
	        if(_field != null){
	            sb.append(_field);
	            sb.append(":");
	        }
	        if(!_isNumeric){
	            sb.append("'");        
	        }
	        sb.append(_value);
	        if(!_isNumeric){
	            sb.append("'");        
	        }
	        if(_isExclude){
	            sb.append(")");
	        }
	        return sb.toString();
        }
}

Listing 3 Value filter implementation

public class SearchQueryFilterOperation implements SearchQueryFilter {
	    
	List<SearchQueryFilter> _filters;
	FilterOperation _operation;
	    
	public SearchQueryFilterOperation(){
	        _operation = FilterOperation.and;
	        _filters = new LinkedList<SearchQueryFilter>();
	}    
	    
	public List<SearchQueryFilter> getFilters() {
	        return _filters;
	}

	public void setFilters(List<SearchQueryFilter> filters) {
	        _filters = filters;
	}

	public void addFilters(SearchQueryFilter filter) {
	        _filters.add(filter);
	}

	public FilterOperation getOperation() {
	        return _operation;
	}

	public void setOperation(FilterOperation operation) {
	        _operation = operation;
	}

        @Override
	public String toString() {
	        StringBuffer sb = new StringBuffer();
	        sb.append("(");
	        sb.append(_operation);
	        for(SearchQueryFilter f : _filters){
	                 sb.append(" ");
	                 sb.append(f);
	        }
	        sb.append(")");
	        return sb.toString();
	}

	public enum FilterOperation{
	        and, or
	}
}

Listing 4 Operation filter implementation


Search Query Value Filter類支援開發者使用等於、小於、大於、區間(同樣支援負值比較)等運算子設定單個欄位的限制。而Search Query Filter Operation類還支援開發者使用AND/OR操作符,將多個Search Query Value Filters和Search Query Filter Operations組合使用。通過這兩個類的組合使用,就能實現亞馬遜雲搜尋所支援的任意查詢過濾器的表示式了。

“分面分類系統支援對一個物件賦予多個特徵(屬性),支援按照多種方式對分類排序,而非按照單一的、預定的分類順序。一個分麵包括‘定義清晰、相互獨立、完全窮盡的方面,某類屬性、特徵或是特定的主題’。【1】例如,藏書可以按照作者,主題,日期等歸類。”

分面分類應用於分面搜尋系統,使用者在這種系統中能夠從多方面進行資訊的導航(譯者注:如書籍可以從作者、主題、出版日期等不同的分面),多方面對應於不同順序的分面。

AWS支援按分面控制搜尋執行以及對搜尋結果排序。同時還支援開發者控制返回的搜尋結果中包含的分面數量。所有的分面操作由Search Query Facet(列表5)這個類來實現。

public class SearchQueryFacet {
	    
          private String _name;
	  private int _maxFacets;
	  private List<String> _constraints;
          private FacetSort _sort;
	    
	  public SearchQueryFacet(String name){
	           _name = name;
	           _maxFacets = -1;
	           _constraints = null;
	           _sort = FacetSort.none;
	  }

	  public SearchQueryFacet(String name, int maxFacets){
	           _name = name;
	           _maxFacets = maxFacets;
	           _constraints = null;
	           _sort = FacetSort.none;
	  }

	  public SearchQueryFacet(String name, int maxFacets, FacetSort sort){
	           _name = name;
	           _maxFacets = maxFacets;
	           _constraints = null;
	           _sort = sort;
	  }

	  public SearchQueryFacet(String name, int maxFacets, List<String> constraints){
	           _name = name;
	           _maxFacets = maxFacets;
	           _constraints = constraints;
	           _sort = FacetSort.none;
	  }

	  public SearchQueryFacet(String name, List<String> constraints){
	           _name = name;
	           _maxFacets = -1;
	           _constraints = constraints;
	           _sort = FacetSort.none;
	  }

	  public SearchQueryFacet(String name, FacetSort sort, List<String> constraints){
	           _name = name;
	           _maxFacets = -1;
	           _constraints = constraints;
	           _sort = sort;
	  }

	  public SearchQueryFacet(String name, FacetSort sort){
	           _name = name;
	           _maxFacets = -1;
	           _constraints = null;
	           _sort = sort;
	  }

	  public String getName() {
	           return _name;
	  }

	  public void setName(String name) {
	           _name = name;
	  }

	  public int getMaxFacets() {
	           return _maxFacets;
	  }

	  public void setMaxFacets(int maxFacets) {
	           _maxFacets = maxFacets;
	  }

	  public FacetSort getSort() {
	           return _sort;
	  }

	  public void setSort(FacetSort sort) {
	           _sort = sort;
	  }

	  public int get_maxFacets() {
	           return _maxFacets;
	  }

	  public void set_maxFacets(int _maxFacets) {
	           this._maxFacets = _maxFacets;
	  }

	  public List<String> getConstraints() {
	           return _constraints;
	  }

	  public void setConstraints(List<String> constraints) {
	           _constraints = constraints;
	  }

	  public void addConstraint(String constraint) {
	           if(_constraints == null)
	                      _constraints = new LinkedList<String>();
	           _constraints.add(constraint);
	  }

	  @Override
	  public String toString(){
	           StringBuffer sb = new StringBuffer();
	           sb.append("&facet=");
	           sb.append(_name);
	           if(_maxFacets > 0){
	                      sb.append("&facet-");
	                      sb.append(_name);
	                      sb.append("-top-n=");
	                      sb.append(_maxFacets);
	           }
	           if((_constraints != null) && (_constraints.size() > 0)){
	                      sb.append("&facet-");
	                      sb.append(_name);
	                      sb.append("-constraints=");
	                      boolean first = true;
	                      for(String c : _constraints){
	                                if(!first)
	                                           sb.append("%2C");
	                                else
	                                           first = false;
	                                sb.append("%27");
	                                sb.append(c);
	                                sb.append("%27");
	                     }
	           }
	           if(!_sort.equals(FacetSort.none)){
	                      sb.append("&facet-");
	                      sb.append(_name);
	                      sb.append("-sort=");
	                      sb.append(_sort);
	           }
	        
	           return sb.toString();
	  }
	    
	  public enum FacetSort{
	           none, alpha, count, max, sum
	  }
}
Listing 5 Facets control class

最後Search Query Sort類(列表6)實現了開發者對結果排序的控制。

public class SearchQuerySort {
	  
	  private List<SearchRank> _ranks;
	    
	  public SearchQuerySort(){
	           _ranks = new LinkedList<SearchRank>();
	  }
	   
	  public void addRank(SearchRank rank){
	           _ranks.add(rank);
	  }
	  @Override
	  public String toString(){
	           if(_ranks.size() == 0)
	                      return null;
	           StringBuffer sb = new StringBuffer();
	           sb.append("&rank=");
	           boolean first = true;
	           for(SearchRank r : _ranks){
	                      if(!first)
	                              sb.append("%2C");
	                      else
	                              first = false;
	                      sb.append(r);
	           }
	           return sb.toString();
	  }

	  public static class SearchRank{
	           private String _name;
	           private boolean _ascending;
	        
	           public SearchRank(){
	                      _ascending = true;
	           }
	           public SearchRank(String name){
	                      _ascending = true;
	                      _name = name;
	           }
	           public SearchRank(String name, boolean ascending){
	                      _ascending = ascending;
	                      _name = name;
	           }
	        
	           @Override
	           public String toString(){
	                      if(_ascending)
	                                return _name;
	                      return "-" + _name;
	           }
	  }
}

Listing 6 Sort control class

CloudSearch查詢除了將所有的引數彙總到一起,還增加了頁碼資訊和一組返回欄位

這個查詢類還提供了一個方法——HTTP查詢轉換(列表7),將搜尋查詢的所有部分彙總,並生成能被搜尋處理的HTTP字串。

public String toHttpQuery() throws Exception{
	        StringBuffer sb = new StringBuffer();
	        sb.append("?results-type=xml");
	        if(_size > 0){
	                   sb.append("&size=");
	                   sb.append(_size);
	        }

	        if(_start > 0){
	                   sb.append("&start=");
	                   sb.append(_start);
	        }

	        if((_fields != null) && (_fields.size() > 0)){
	                   sb.append("&return-fields=");
	                   boolean first = true;
	                   for(String f : _fields){
	                              if(!first)
	                                          sb.append("%2C");
	                              else
	                                          first = false;
	                              sb.append(f);
	                   }
	        }

	        if(_filter != null){
	                   if(_filter instanceof SearchQueryValueFilter)
	                                          sb.append("&q=");
	                   else
	                                          sb.append("&bq=");
	                   sb.append(URLEncoder.encode(_filter.toString(), "UTF8"));
	        }

	        if((_facets != null) && (_facets.size() > 0)){
	                   for(SearchQueryFacet f : _facets){
	                                          sb.append(f);
	                   }
	        }

	        if((_sorts != null) && (_sorts.size() > 0)){
	                   for(SearchQuerySort s : _sorts){
	                                          sb.append(s);
	                   }
	        }

	        return sb.toString();
	    }

Listing 7 Convert to HTTP query method

測試我們的API

我們使用亞馬遜提供的IMDB樣例來進行驗證。首次單元測試(列表8)用於驗證我們實現的搜尋API。

public class SearchAPITester extends TestCase {

	    private static final String SearchURL = "search-imdb-movies-ab4fpqw4eocczpgsnrtlu4rn7i.us-east-
 1.cloudsearch.amazonaws.com";
	    private CloudSearchClient client;

            protected void setUp() throws Exception {
	        client = new CloudSearchClient(SearchURL);
	    }

	    protected void tearDown() {
	        client.close();
	    }

	    public void testSearch() throws Exception{

	        SearchQueryValueFilter f1 = new SearchQueryValueFilter("title", "star", false, false);
	        SearchQueryValueFilter f11 = new SearchQueryValueFilter("title", "war", false, true);
	        SearchQueryValueFilter f2 = new SearchQueryValueFilter("year", "..2000", true, false);
	        SearchQueryFilterOperation f12 = new SearchQueryFilterOperation();
	        f12.setOperation(FilterOperation.or);
	        f12.addFilters(f1);
	        f12.addFilters(f11);        
	        SearchQueryFilterOperation f3 = new SearchQueryFilterOperation();
	        f3.addFilters(f12);
	        f3.addFilters(f2);

	        
	        CloudSearchQuery query = new CloudSearchQuery(f3);
	        query.addField("actor");
	        query.addField("director");
	        query.addField("title");
	        query.addField("year");

	        
	        SearchQueryFacet sf = new SearchQueryFacet("genre", 5, FacetSort.alpha);
	        sf.addConstraint("Drama");
	        sf.addConstraint("Sci-Fi");
	        query.addFacet(sf);
	        

	        SearchQuerySort sort = new SearchQuerySort();
	        SearchRank r1 = new SearchRank("title");
	        SearchRank r2 = new SearchRank("year", false);
	        sort.addRank(r1);
	        sort.addRank(r2);
	        query.addSort(sort);
	        
	        try {
	            System.out.println("Test 1 ");
	            SearchResults result = client.search(query);
	            System.out.println(result);
	        } catch (Exception e) {
	            e.printStackTrace();
	        }
	    }
}
Listing 8 Search API tester

該測試獲得的結果(列表9),和直接通過亞馬遜REST API獲得的結果相同。

SearchResults 
[ID=6ddcaa561c05c4cc3dae0f2d67b89419fbfea467ac6292b612dfb3a4a547692c6bea0194d6d37630b171b100197578dc, hitcount=1942, start=0, expression=(and (or title:'star' (not title:'war')) year:..2000), execution time=35ms, cpu execution time=0ms 
Hit [ID=tt0092493, values={title:['Crocodile' Dundee II]year:[1988]actor:[Blinco, Maggie,Dingo, Ernie,Hogan, Paul,Holt, Jim,Kozlowski, Linda,Meillon, John,Mercurio, Gus,Rackman, Steve,Scavone, Anthony,Skilton, Gerry,Wilson, Alec]director:[Cornell, John]}] 
Hit [ID=tt0078718, values={title:[...And Justice for All.]year:[1979]actor:[Bryggman, Larry,Christian, Robert,Forsythe, John,Lahti, Christine,Levene, Sam,Pacino, Al,Strasberg, Lee,Tambor, Jeffrey,Waites, Thomas G.,Warden, Jack,Williams, Jonathan]director:[Jewison, Norman]}] 
Hit [ID=tt0078721, values={title:[10]year:[1979]actor:[Andrews, Julie,Crosby, Denise,Daly, Rad,Dennehy, Brian,Derek, Bo,Haven, Annette,Jones, Sam J.,LeMay, Dorothy,Money, Constance,Moore, Dudley,Royalle, Candida,Serena,Showalter, Max,Volz, Nedra,Wallace, Dee,Webber, Robert]director:[Edwards, Blake]}] 
Hit [ID=tt0147800, values={title:[10 Things I Hate About You]year:[1999]actor:[Babin, Michelle,Bennett, Tim,Blake, Shelsie,Gordon-Levitt, Joseph,Junger, Gil,Keegan, Andrew,Kountz, Daniel,Krumholtz, David,Ledger, Heath,Magnuson, Katy,Matthews, Amber,Miller, Larry,Mitchell, Daryl,O'Neill, Bridget,Oleynik, Larisa,Pratt, Susan May,Snider, Tommy,Stiles, Julia,Union, Gabrielle,Zorich, Jay]director:[Junger, Gil]}] 
Hit [ID=tt0214388, values={title:[100 Girls]year:[2000]actor:[Billman, Ange,Chriqui, Emmanuelle,DeBello, James,Graham, Aimee,Grant, Tanisha,Green, Johnny,Heigl, Katherine,Hiraizumi, Gina,Musiala, Agnieszka,Oleynik, Larisa,Pressly, Jaime,Ribisi, Marissa,Tucker, Jonathan]director:[Davis, Michael]}] 
Hit [ID=tt0115433, values={title:[101 Dalmatians]year:[1996]actor:[Close, Glenn,Daniels, Jeff,Fielder, Harry,Fraser, Hugh,Laurie, Hugh,McInnerny, Tim,Mullard, Arthur,Plowright, Joan,Richardson, Joely,Richardson, Laurence,Shrapnel, John,Weiss, Zohren,Welker, Frank,Williams, Mark]director:[Herek, Stephen]}] 
Hit [ID=tt0050083, values={title:[12 Angry Men]year:[1957]actor:[Balsam, Martin,Begley, Ed,Binns, Edward,Bond, Rudy,Cobb, Lee J.,Fiedler, John,Fonda, Henry,Kelly, James,Klugman, Jack,Marshall, E.G.,Nelson, Billy,Savoca, John,Sweeney, Joseph,Warden, Jack]director:[Lumet, Sidney]}] 
Hit [ID=tt0103594, values={title:[1492: Conquest of Paradise]year:[1992]actor:[Assante, Armand,Dean, Loren,Depardieu, Gérard,Dunn, Kevin,Karyo, Tchéky,Langella, Frank,Molina, Ángela,Montero, Silvia,Rey, Fernando,Weaver, Sigourney,Wincott, Michael]director:[Scott, Ridley]}] 
Hit [ID=tt0078723, values={title:[1941]year:[1979]actor:[Aykroyd, Dan,Beatty, Ned,Belushi, John,Caan, James,Cheshire, Denise,Gary, Lorraine,Hamilton, Murray,Lassick, Sydney,Lauren, Mo,Lee, Christopher,Marshall, Penny,Matheson, Tim,Mifune, Toshirô,Moriarty, Steve,Oates, Warren,Robinson, Hank,Rothstein, Debbie,Stack, Robert]director:[Spielberg, Steven]}] 
Hit [ID=tt0046672, values={title:[20000 Leagues Under the Sea]year:[1954]actor:[Cooper, Ted,Daheim, John,Douglas, Kirk,Gargan, Jack,Graham, Fred,Harvey, Harry,Helton, Percy,Kerrigan, J.M.,Lorre, Peter,Lukas, Paul,Lummis, Dayton,Marr, Eddie,Mason, James,Mitchell, Laurie,Pall, Gloria,Pennick, Jack,Vigran, Herb,Wilke, Robert J.,Young, Carleton,de Corsia, Ted]director:[Fleischer, Richard]}] 
Facet [name=genre, values={(Sci-Fi,237},(Drama,1063}}] 
] 


列表9 搜尋API測試結果

第二次測試(列表10)用來驗證文件的新增和刪除。

public class DocumentAPITester extends TestCase {

	    private static final String DocumentURL = "doc-imdb-movies-ab4fpqw4eocczpgsnrtlu4rn7i.us-east-
 1.cloudsearch.amazonaws.com";
	    private CloudSearchDocumentClient client;
	    private BatchType batch;
	    
	    protected void setUp() throws Exception {
	        client = new CloudSearchDocumentClient(DocumentURL);
	        
	        FieldType title = new FieldType();
	        title.setName("title");
	        title.setValue("The Seeker: The Dark Is Rising");
	        FieldType director = new FieldType();
	        director.setName("director");
	        director.setValue("Cunningham, David L.");
	        FieldType genrea = new FieldType();
	        genrea.setName("genre");
	        genrea.setValue("Adventure");
	        FieldType genred = new FieldType();
	        genred.setName("genre");
	        genred.setValue("Drama");
	        FieldType genref = new FieldType();
	        genref.setName("genre");
	        genref.setValue("Fantasy");
	        FieldType genret = new FieldType();
	        genret.setName("genre");
	        genret.setValue("Thriller");
	        FieldType actor1 = new FieldType();
	        actor1.setName("actor");
	        actor1.setValue("McShane, Ian");
	        FieldType actor2 = new FieldType();
	        actor2.setName("actor");
	        actor2.setValue("Eccleston, Christopher");
	        FieldType actor3 = new FieldType();
	        actor3.setName("actor");
	        actor3.setValue("Conroy, Frances");
	        FieldType actor4 = new FieldType();
	        actor4.setName("actor");
	        actor4.setValue("Conroy, Frances");
	        FieldType actor5 = new FieldType();
	        actor5.setName("actor");
	        actor5.setValue("Ludwig, Alexander");
	        FieldType actor6 = new FieldType();
	        actor6.setName("actor");
	        actor6.setValue("Crewson, Wendy");
	        FieldType actor7 = new FieldType();
	        actor7.setName("actor");
	        actor7.setValue("Warner, Amelia");
	        FieldType actor8 = new FieldType();
	        actor8.setName("actor");
	        actor8.setValue("Cosmo, James");
	        FieldType actor9 = new FieldType();
	        actor9.setName("actor");
	        actor9.setValue("Hickey, John Benjamin");
	        FieldType actor10 = new FieldType();
	        actor10.setName("actor");
	        actor10.setValue("Piddock, Jim");
	        FieldType actor11 = new FieldType();
	        actor11.setName("actor");
	        actor11.setValue("Lockhart, Emma");
	        AddType add = new AddType();
	        add.setId("tt0484562");
	        add.setVersion(1l);
	        add.setLang("en");
	        add.getField().add(title);
	        add.getField().add(director);
	        add.getField().add(genrea);
	        add.getField().add(genred);
	        add.getField().add(genref);
	        add.getField().add(genret);
	        add.getField().add(actor1);
	        add.getField().add(actor2);
	        add.getField().add(actor3);
	        add.getField().add(actor4);
	        add.getField().add(actor5);
	        add.getField().add(actor6);
	        add.getField().add(actor7);
	        add.getField().add(actor8);
	        add.getField().add(actor9);
	        add.getField().add(actor10);
	        add.getField().add(actor11);
	        
	        DeleteType delete = new DeleteType();
	        delete.setId("tt0301199");
	        delete.setVersion(1l);
	        
	        batch = new BatchType();
	        batch.getAdd().add(add);
	        batch.getDelete().add(delete);

	    }

	    protected void tearDown() {
	        client.close();
	    }

	    public void testSearch() throws Exception{

	        try {
	            System.out.println("Test 1 ");
	            ResponseType result = client.index(batch);
	            System.out.println("Status " + result.getStatus() + " Added " + result.getAdds() + " Deleted " +
result.getDeletes());
	        } catch (Exception e) {
	            e.printStackTrace();
	        }
	   }
}

Listing 10 Document upload tester

測試也獲得了預期的結果(列表11)

Status SUCCESS Added 1 Deleted 1
Listing 11 Document upload test results

總結

上面這些簡單的Java API實現了亞馬遜雲搜尋的功能,顯著簡化了亞馬遜雲搜尋功能在已有Java應用中的使用,必然會擴大應用的影響範圍。

關於作者

Boris Lublinsky博士是諾基亞首席架構師,主要從事大資料、SOA、BPM、中介軟體的實現。在此之前Boris曾經是Herzum軟體公司的首席架構師,為客戶設計大規模的SOA系統,曾負責CNA保險公司的企業架構,參與了CNA的系統整合與SOA策略的設計和實現,構建了應用框架並實現了面向服務的架構。Boris在企業、技術架構,軟體工程方面有超過25年的經驗。他還是OASIS SOA參考模型技術委員會的活躍會員,也是《Applied SOA:Service-Oriented Architecture and Design Strategies》,一書的共同作者。他還發表了大量架構、程式設計、大資料、SOA、BPM的相關文章。