Sun JNDI教程翻譯 第二部分 Directory Operations

本文是JNDI Tutorial系列文章的第二部分:The Basics,介紹了JNDI的一些基礎知識,諸如Naming操作和Directory操作。介紹瞭如何通過編程的方式訪問命名和目錄服務,如何使用JNDI和目錄進行交互。從準備環境到查找對象以及在目錄中進行搜索等操作。
本部分主要講述了JNDI對目錄的操作。
可以通過JNDi來實現以下目錄操作:
接下來將介紹這些操作的具體內容,在使用它們一下,首先應該瞭解一下有關屬性名稱的知識。
配置
本部分教程的內容使用LDAP服務provider。本部分的例子都是假設已經根據前面的預備知識創建了樣例名稱空間。如果使用其他的服務provider或者使用其他的LDAP名稱空間,那麼這部分的例子的執行結果可能會和實際有所不同或者不能執行。
通過以下環境屬性來初始化這部分例子中的initial context
//Set up the environment for creating the initial context
env.put(Context.INITIAL_CONTEXT_FACTORY,
                                                                           "com.sun.jndi.ldap.LdapCtxFactory");
                   env.put(Context.PROVIDER_URL, "ldap://localhost:389/o=JNDITutorial");
                   env.put(Context.SECURITY_AUTHENTICATION, "simple");
                   env.put(Context.SECURITY_PRINCIPAL, "uid=admin,ou=system");
env.put(Context.SECURITY_CREDENTIALS, "secret");
DirContext ctx = new InitialDirContext(env);
(此處教程中使用匿名用戶進行的綁定,而ApacheDS默認情況下不允許匿名綁定,所以筆者使用管理員進行綁定,實際上,根據不同的服務器,本教程中所有的例子的返回結果可能和原JNDI教程有些不一致)
屬性名(Attribute Names)
一個屬性包含屬性標識符合一系列屬性值,屬性標識符也叫做屬性名,是一個字符串用來標識一個屬性。屬性值是屬性的內容,並且屬性的值得類型不侷限於字符串。當在執行檢索、獲取或者修改的時候需要指定特定的屬性,這個時候需要使用屬性名。返回屬性的操作也會返回名稱,例如讀操作或者檢索操作。
當使用屬性名時,需要注意特定目錄服務器的特性,所以有時可能會返回不同的結果。這些特性在後面將會介紹。
屬性類型
在諸如LDAP的目錄中,屬性名標識了屬性的類型,並且叫做屬性類型名。例如,屬性名cn叫做屬性類型名。屬性的類型定義指定了屬性的值(是否可以有多個值),等價性,以及排序規則(當根據屬性值進行執行比較和排序操作時)
屬性子類
一些目錄實現支持屬性子類,這些服務器允許屬性類型根據其他的屬性類型進行定義。例如,name屬性可能是所有名稱相關屬性的超類,例如commonName可能是name的一個子類。對於支持屬性子類的目錄實現,當請求name屬性的時候,可能返回commonName屬性。
當訪問支持屬性子類的目錄時,要注意服務器可能返回與要求的屬性名不同的屬性,爲了最小程度的減少這種情況,使用最底層的子類。
同義的屬性名
一些目錄實現支持屬性名同義的概念,例如cn屬性是commonName的一個同義屬性。因此請求cn屬性時,可能返回commonName屬性。
當訪問支持這個特性的目錄時,必須注意服務器可能返回和請求屬性名不同的屬性。爲了防止這種情況的發生,使用規範的屬性名來代替和它同義的屬性名。規範屬性名用於屬性定義,而同義的屬性名是參考了規範屬性名的定義。
語言參數選擇
LDAP v3的一個擴展允許在屬性名中指定語言代碼。這和屬性子類相似,一個屬性名可以代表多個不同的屬性。一個例子就是description屬性可以有以下兩種語言的形式:
description:software
description;lang-en:software products
description;lang-de:Softwareprodukte
當請求description屬性時,可能返回所有這三種結果。
當訪問支持這種特性的目錄時,必須注意服務器可能返回和期望屬性名不同的屬性。
讀取屬性
當讀取目錄中一個對象的屬性時,需要將對象名傳給DirContext.getAttributes()方法作參數。假設有一個名爲”cn=Ted Geisel,ou=people”的對象,爲了獲取它的屬性,需要使用如下的代碼:
Attributes answer = ctx.getAttributes("cn=Ted Geisel, ou=People");
通過下面的代碼迭代打出所有的屬性名和屬性值:
for (NamingEnumeration ae = attrs.getAll(); ae.hasMore();) {
         Attribute attr = (Attribute) ae.next();
         System.out.println("attribute: " + attr.getID());
         /* print each value */
for (NamingEnumeration e = attr.getAll(); e.hasMore(); System.out
                                                                                                                         .println("value: " + e.next()));
}
執行的結果如下:
attribute: telephonenumber
value: +1 408 555 5252
attribute: mail
attribute: facsimiletelephonenumber
value: +1 408 555 2329
Eclipse中查看的視圖如下:
(從圖中可以看出,該對象的屬性不僅僅上面列出的這些,但是ApacheDS在返回結果的時候只返回了一部分。筆者認爲ApacheDS的理由是:既然你提供了用戶的DN來進行讀取屬性,那麼諸如cnsncouo等屬性就應該是已知的了,沒有必要再返回一次。)
 
返回指定的屬性:
爲了運行所有屬性的一個子集,可以指定一個屬性名數組來指定需要獲取的屬性:
// Specify the ids of the attributes to return
                   String[] attrIDs = { "sn", "telephonenumber", "golfhandicap",
                                                                                             "mail" };
                   // Get the attributes requested
                   Attributes answer = ctx.getAttributes("cn=Ted Geisel, ou=People",
                                                                                    attrIDs);
返回結果如下:
attribute: telephonenumber
value: +1 408 555 5252
attribute: mail
attribute: sn
value: Geisel
由於這個對象沒有golfhanicap屬性,所以只返回三個屬性。
修改屬性
DirContext接口包含了修改對象的屬性及屬性值得方法。
使用修改列表
修改一個對象的屬性的方法之一就是提供一個修改請求列表(ModificationItem)。每個ModificationItem由一定數量的指定修改類型的常量以及修改的屬性Attribute組成。下面是三種類型的修改:
¨         ADD_ATTRIBUTE
¨         REPLACE_ATTRIBUTE
¨         REMOVE_ATTRIBUTE
ModificationItem(int mod_op, Attribute attr)
          Creates a new instance of ModificationItem.
修改的順序和它們出現在列表中的順序一致,要麼所有的修改都完成,要麼一個都不完成,即一個修改操作是一個事務。
下面的代碼創建了一個修改列表,將屬性mail的值修改爲[email protected],添加了一個telephonenumber屬性值,並刪除了jpegphoto屬性。
                   // Create the initial context
                   DirContext ctx = new InitialDirContext(env);
                   String name = "cn=Ted Geisel, ou=People";
                   // Save original attributes
              Attributes orig = ctx.getAttributes(name, new String[] { "mail","telephonenumber", "jpegphoto" });
              // Specify the changes to make
                   ModificationItem[] mods = new ModificationItem[3];
              // Replace the "mail" attribute with a new value
                   mods[0] = new ModificationItem(DirContext.REPLACE_ATTRIBUTE,
                                                                                             new BasicAttribute("mail", "[email protected]"));
              // Add additional value to "telephonenumber"
                   mods[1] = new ModificationItem(DirContext.ADD_ATTRIBUTE,
                                                                                             new BasicAttribute("telephonenumber", "+1 555 555 5555"));
              // Remove the "jpegphoto" attribute
                   mods[2] = new ModificationItem(DirContext.REMOVE_ATTRIBUTE,
                                                                                             new BasicAttribute("jpegphoto"));
              // Perform the requested modifications on the named object
當創建完修改列表後,執行modifyAttributes()方法,該方法接受兩個參數,一個爲String類型的對象名,另一個爲ModificationItem[]類型的修改列表。執行完該方法後,修改被應用。
ctx.modifyAttributes(name, mods);
修改完成後打印新舊屬性的比較:
**** new attributes *****
attribute: telephonenumber
value: +1 408 555 5252
value: +1 555 555 5555
attribute: mail
**** reverted to original attributes *****
attribute: telephonenumber
value: +1 408 555 5252
attribute: mail
attribute: jpegphoto
value: [B@119cca4
windows active directory中,telephonenumber屬性是一個單值屬性,但是RFC 2256中的定義是多值的。所以當LDAP服務器爲win ad時,需要將添加操作修改爲替換操作。
使用屬性
也可以通過制定修改的類型以及要修改的屬性來執行修改操作。
例如,下面的方法將名稱爲name的對象的屬性(orig中定義的同名的屬性)替換爲orig中的屬性,該對象的orig以外的屬性不變。
ctx.modifyAttributes(name,DirContext.REPLACE_ATTRIBUTE,orig);
檢索操作
最常見的目錄服務就是黃頁,或者說檢索服務。
[yellow page: 黃頁(電話)查號簿(美國按行業、職業分類的部分常用黃色紙)]
可以組合一個檢索語句,語句中包含實體的屬性需要符合的條件,然後提交給目錄服務器,目錄服務器返回符合條件的檢索列表。例如,可以查找所有surname”Sch”開頭的人的實體,或者保齡球平均成績高於200的實體。
DirContext接口提供了很多檢索的方法,功能和複雜性各不相同。目錄檢索的相關內容包括以下部分:
¨         基本檢索
¨         檢索過濾器
¨         檢索控制
基本檢索
這是目錄檢索的最簡單的形式,只需要指定檢索結果必須包含的屬性以及檢索的目的上下文。下面的代碼創建了一個屬性集matchAttrs,其中包含兩個屬性,telephonenumbermail,檢索指定了實體必須具有的surname(sn)屬性和mail屬性,並且surname屬性的值爲”Geisel”,而mail的屬性值任意。然後調用DirContext.search()方法在ou=people的上下文中檢索符合matchAttrs的實體。
Attributes matchAttrs = new BasicAttributes(true); // ignore case
matchAttrs.put(new BasicAttribute("sn", "Geisel"));
matchAttrs.put(new BasicAttribute("mail"));
// Search for objects that have those matching attributes
NamingEnumeration answer = ctx.search("ou=People", matchAttrs);
可以使用下面的語句打印結果:
while (answer.hasMore()) {
SearchResult sr = (SearchResult) answer.next();
System.out.println(">>>" + sr.getName());
GetattrsAll.printAttrs(sr.getAttributes());
}
返回結果爲:
>>>cn=Ted Geisel
attribute: telephonenumber
value: +1 408 555 5252
attribute: mail
attribute: facsimiletelephonenumber
value: +1 408 555 2329
attribute: objectClass
value: person
value: inetOrgPerson
value: organizationalPerson
value: top
attribute: jpegphoto
value: [B@179c285
attribute: sn
value: Geisel
attribute: cn
value: Ted Geisel
可以選擇返回指定的屬性,代碼如下:
// Specify the ids of the attributes to return
String[] attrIDs = { "sn", "telephonenumber", "golfhandicap","mail" };
answer = ctx.search("ou=People", matchAttrs, attrIDs);
此時返回的結果爲:
>>>cn=Ted Geisel
attribute: telephonenumber
value: +1 408 555 5252
attribute: mail
attribute: sn
value: Geisel
 
檢索過濾器
和基本檢索不同的是,可以使用一個檢索過濾器進行檢索操作。檢索過濾器的檢索語句是通過邏輯表達式的形式表示的。檢索過濾器的語法在RFC 2254中進行了定義,DirContext.search()方法接受符合這個語法的過濾器。
下面的檢索過濾器指定了合法的實體必須具有sn屬性,並且其值爲Geisel以及一個值爲任意的mail屬性:(&(sn=Geisel)(mail=*))
下面的代碼創建了一個過濾器和一個默認的檢索控制,SearchControls,並使用它們來進行檢索操作。這個檢索和基本檢索中的功能是一樣的。
// Create default search controls
SearchControls ctls = new SearchControls();
// Specify the search filter to match
// Ask for objects with attribute sn == Geisel and which have
// the "mail" attribute.
String filter = "(&(sn=Geisel)(mail=*))";
// Search for objects using filter
NamingEnumeration answer = ctx.search("ou=People", filter, ctls);
// Print the answer
while (answer.hasMore()) {
         SearchResult sr = (SearchResult) answer.next();
         System.out.println(">>>" + sr.getName());
         GetattrsAll.printAttrs(sr.getAttributes());
}
檢索返回的結果和基本檢索的結果是一樣的。
檢索過濾器語法一覽
檢索過濾器語法基本上就是邏輯運算符作爲前綴的邏輯表達式,下圖是一些邏輯運算符號及其說明:
過濾器中的每一項都由屬性標識符和屬性值或者上述的運算符號組成。例如,”sn=Geisel”表示必須具有sn屬性,並且sn屬性的值必須爲Geisel”mail=*”則表示mail屬性只要存在就可以了。
每一項都必須使用括號括起來使用。邏輯運算符可以組合使用,通過使用括號進行分割。例如
(| (& (sn=Geisel) (mail=*)) (sn=L*))
關於更完整的語法描述,參見RFC 2254
返回指定屬性
可以通過設置SearchControlssetReturningAttributes()方法來設置需要返回的特定屬性:
// Specify the ids of the attributes to return
String[] attrIDs = { "sn", "telephonenumber", "golfhandicap","mail" };
ctls.setReturningAttributes(attrIDs);
其返回的結果和基本認證返回的一致。
 
檢索控制
在上面的檢索過濾器中如何使用SearchControls參數來選擇需要返回的屬性。還可以是SearchControls參數來控制檢索的其他方面,下面是一些可用的控制:
¨         返回的屬性
¨         檢索的範圍
¨         返回的最大結果數
¨         等待最大時間(毫秒),即超時時間
¨         是否返回和實體相關聯的Java對象
¨         在檢索時是否廢除JNDI鏈接
其中後兩部分在之後的教程中介紹,本部分教程介紹前四種控制:
檢索範圍
通過使用SearchControls.setSearchScope(int scope)方法來設置檢索範圍,檢索範圍由三種,分別如下:
static int OBJECT_SCOPE
                           
檢索指定的對象
static int ONELEVEL_SCOPE
                           
檢索指定對象及其直接子實體
static int SUBTREE_SCOPE
                           
在以指定對象爲根的子樹中進行檢索
下圖摘自ApacheDS網站對於檢索範圍的說明,比較易懂:
Note:Base scope is useful for reading attribute values of a particular entry. One example for using One_Level scope is expanding the tree in graphical LDAP clients. For extensive investigations within your data, Subtree is the most powerful option.
這裏的示例就省略了,通過上面的圖片應該很容易理解檢索範圍的概念。
 
數量限制
有的時候,需要限制查詢返回結果的數量,這個時候需要使用檢索控制的數量限制。默認情況下,檢索操作返回所有符合條件的檢索結果,可以使用SearchControls.setCountLimit()方法設置檢索結果的數量。下面的代碼將檢索結果數量設置爲1
// Set search controls to limit count to 'expected'
SearchControls ctls = new SearchControls();
ctls.setCountLimit(expected);
如果程序要獲取大於最大數量的結果,那麼將拋出一個SizeLimitExceededException。所以當設置數量限制的時候,應該將這個異常和NamingExceptions異常相區分,或者根據數量限制請求結果(不要超過最大數量)
指定檢索結果的數量是一種控制程序消耗資源的方法,比如內存或者網絡帶寬。其他控制資源消耗的辦法有實用查詢過濾器、在適當的上下文中查詢以及使用適當的檢索範圍。
(筆者運行這部分Sun的教程,和實際不一樣,原因有待調查,情況如下:即使我設置了數量限制,但是還是可以返回兩個結果。)
但是當筆者將檢索過濾器修改爲objectClass=*的時候就可以使用了。
程序運行結果爲:
>>>cn=Ted Geisel
number of answers: 1
源代碼如下:
package com.sun.jndi.examples.basics;
 
import java.util.Hashtable;
 
import javax.naming.Context;
import javax.naming.NamingEnumeration;
import javax.naming.NamingException;
import javax.naming.SizeLimitExceededException;
import javax.naming.directory.DirContext;
import javax.naming.directory.InitialDirContext;
import javax.naming.directory.SearchControls;
import javax.naming.directory.SearchResult;
 
/**
 * Demonstrates how to perform a search and limit the number of results
 * returned.
 *
 * usage: java SearchCountLimit
 */
class SearchCountLimit {
         static int expected = 1;
 
         public static void printSearchEnumeration(NamingEnumeration enu) {
                            int count = 0;
                            try {
                                               while (enu.hasMore()) {
                                                                 SearchResult sr = (SearchResult) enu.next();
                                                                 System.out.println(">>>" + sr.getName());
                                                                 ++count;
                                               }
                                               System.out.println("number of answers: " + count);
                            } catch (SizeLimitExceededException e) {
                                               if (count == expected)
                                                                 System.out.println("number of answers: " + count);
                                               else
                                                                 e.printStackTrace();
                            } catch (NamingException e) {
                                               e.printStackTrace();
                            }
         }
 
         public static void main(String[] args) {
 
                            // Set up the environment for creating the initial context
                            Hashtable env = new Hashtable();
                            env.put(Context.INITIAL_CONTEXT_FACTORY,
                                                                 "com.sun.jndi.ldap.LdapCtxFactory");
                            env.put(Context.PROVIDER_URL, "ldap://localhost:389/o=JNDITutorial");
                            env.put(Context.SECURITY_AUTHENTICATION, "simple");
                            env.put(Context.SECURITY_PRINCIPAL, "uid=admin,ou=system");
                            env.put(Context.SECURITY_CREDENTIALS, "sunjavaduke");
 
                            try {
                                               // Create initial context
                                               DirContext ctx = new InitialDirContext(env);
                                               // Set search controls to limit count to 'expected'
                                               SearchControls ctls = new SearchControls();
                                               ctls.setCountLimit(expected);
                                               // Search for objects with those matching attributes
                                               NamingEnumeration answer = ctx.search("ou=People", "(sn=M*)", ctls);
                                               // Print the answer
                                               printSearchEnumeration(answer);
                                               // Close the context when we're done
                                               ctx.close();
                            } catch (Exception e) {
                                               System.err.println(e);
                            }
         }
}
(運行上面的程序時,打印結果爲:
>>>cn=Spuds Mackenzie
>>>cn=Londo Mollari
number of answers: 2
並沒有拋出異常)
設置超時
                   設置超時是爲了不至於等待太長的時間,如果超過時間限制,操作還沒有執行完的話,將拋出TimeLimitExceededException,下面的代碼設置了時間限制:
ctls.setTimeLimit(1000); //set time limit to 1 second
爲了使這個程序運行,需要做一些特殊的設置,例如使用較慢的服務器或者檢索範圍使用SUB_TREE檢索,並且服務器最好包含大量的實體。
當設置時間限制爲0的時候,則表示沒有時間限制。
集成命名和目錄操作
命名操作部分介紹瞭如何使用Context接口的bind(),rebind(),createSubcontext()方法來創建綁定和子上下文。DirContext接口包含了這些方法的重載方法,接受屬性參數。可以使用這些DirContext方法關聯屬性和對象。例如,可以創建一個Person對象,並將其綁定到一個名稱空間,同時添加一些Person對象的屬性。
創建一個包含屬性的上下文
通過使用DirContext.createSubcontext()方法創建一個包含屬性的上下文,提供要創建的上下文的名字和屬性作爲參數:
// Create the initial context
DirContext ctx = new InitialDirContext(env);
// Create attributes to be associated with the new context
Attributes attrs = new BasicAttributes(true); // case-ignore
Attribute objclass = new BasicAttribute("objectclass");
objclass.add("top");
objclass.add("organizationalUnit");
attrs.put(objclass);
// Create the context
Context result = ctx.createSubcontext("ou=Fruits", attrs);
上面的代碼創建了一個叫做ou=Fruits的上下文,幷包含具有兩個值得屬性objectClass,值分別爲toporganizationalUnit,這個新的上下文被追加到ctx上下文中,然後列出ctx下面的內容,結果如下:
ou=Groups: javax.naming.directory.DirContext
ou=People: javax.naming.directory.DirContext
ou=Fruits: javax.naming.directory.DirContext
可以看到新建的Context已經添加成功。
添加包含屬性的綁定
DirContext.bind()方法用來在上下文中添加一個包含屬性的綁定。該方法的參數是對象的名字被綁定的對象以及一個屬性集合
// Create the initial context
DirContext ctx = new InitialDirContext(env);
// Create object to be bound
Fruit fruit = new Fruit("orange");
// Create attributes to be associated with object
Attributes attrs = new BasicAttributes(true); // case-ignore
Attribute objclass = new BasicAttribute("objectclass");
objclass.add("top");
objclass.add("organizationalUnit");
attrs.put(objclass);
// Perform bind
ctx.bind("ou=favorite, ou=Fruits", fruit, attrs);
上面的代碼創建了一個Fruit類的對象fruit,並使用ou=favorite名字將其綁定到ou=Fruits的上下文。這個上下文相對於ctx,及o=JNDITutorial。這個綁定具有objectclass屬性。綁定完成後查找ou=favorite,ou=Fruits,將會獲取fruit對象。如果獲取ou=favorite,ou=Fruits的屬性,那麼將會獲取新建的對象的屬性:
orange
attribute: ou
value: favorite
attribute: objectClass
value: javaNamingReference
value: javaObject
value: organizationalUnit
value: top
attribute: javafactory
value: com.sun.jndi.examples.basics.FruitFactory
attribute: javareferenceaddress
value: #0#fruit#orange
attribute: javaclassname
value: com.sun.jndi.examples.basics.Fruit
(當運行這個程序兩次的話,會提示錯誤信息NameAlreadyBoundException,這是由於ou=favorite已經被綁定到ou=Fruits上下文。)
可以使用rebind進行重新綁定。
有關JNDI的另外一篇不錯的文章,請參考:
http://www.javaworld.com/javaworld/jw-03-2000/jw-0324-ldap.html?page=1
  
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章