WEB开发网
开发学院软件开发Java SBLIM Client 2 之最佳实践 阅读

SBLIM Client 2 之最佳实践

 2009-12-18 00:00:00 来源:WEB开发网   
核心提示:SBLIM CIM Client 是一个被广泛运用于系统配置管理领域的 WBEM 服务客户端,为了符合标准的 JSR48 规范,SBLIM Client 2 之最佳实践,SBLIM CIM Client 2.x 版本于去年正式推出,而原有的 1.x 本版将只做维护,希望能给读者对于 SBLIM CIM Client 2

SBLIM CIM Client 是一个被广泛运用于系统配置管理领域的 WBEM 服务客户端。为了符合标准的 JSR48 规范,SBLIM CIM Client 2.x 版本于去年正式推出,而原有的 1.x 本版将只做维护。新版本的软件在我们带来新功能和新特性的同时,也对用户使用的方法提出了一些新的要求。有些要求显而易见,而有些要求却并不那么明显。

SBLIM Client 概貌

SBLIM CIM Client 是一个用 JAVA 实现的 WBEM 服务客户端,一般位于 CIM 程序结构的最顶端,如图 1。因为其开源,简单易用等特性,被广泛运用于各种基于 CIM/SMIS 的管理软件。用户可以方便的处理和 CIM 服务器的通信,而不用理会 CIM 消息的封装和解析,从而专注于业务逻辑的开发。同时,SBLIM CIM Client 提供了丰富的配置功能,用户可以按自己的需求对连接池,安全性,xml 解析方式,日志记录等做定制。

为了符合标准的 JSR48 规范,去年 12 月,SBLIM CIM Client 推出了 2.x 版本。新版本的 API 对 1.x 的 API 不做后向兼容。本文中的所有例子都将采用 2.1.x 版本,该版本基于 JAVA 1.5. 在新版的 SBLIM Client 中,客户端的配置和初始化、查询结果容器、CIM 事件等特性都有了一定程度的变化,更符合使用习惯。


图 1. CIM 程序常见结构
SBLIM Client 2 之最佳实践

配置和初始化客户端

SBLIM CIM Client 2.x 版本引入了全新的客户端配置和初始化过程。新标准中摒弃了客户端标准类 CIMClient 的使用,取而代之的是客户端接口 WBEMClient,该接口丰富了其中部分方法的定义,可是所有这些被暴露出来的方法都不具备向后兼容性,所以用户不得不为此修改他们原有的代码。

WBEMClient 的初始化工作是通过一个工厂类 WBEMClientFactory 完成的,该类为 WBEMClient 提供了对某个特殊协议的实现,下面是如何使用该工厂类的一个简单实例,用户只需要调用静态函数 getClient(),并指名具体使用的协议即可。


清单 1. 工厂类 WBEMClientFactory 创建 WBEMClient 示例
 WBEMClient cc = null; 
 
 try { 
 cc = WBEMClientFactory.getClient("CIM-XML"); 
 } catch (Exception e) { 
 System.out.println("Received error when trying to retrieve client handle"); 
 System.exit(-1); 
 } 
 
 cc.initialize(cns, s, null); 

下面的代码给出了一个详细的 WBEMClient 初始化过程,用户不仅需要指名 WBEM 服务的 URL 地址,同时需要设定验证 WBEM 服务的用户名和密码。请注意,整个初始化过程并没有导致客户端和 CIMOM 之间的通讯,只有当第一个操作被执行的时候,第一个请求才会被发送到 CIMOM。如果连接成功,该函数将返回一个 WBEMClient 的实例,如果失败,则直接返回 null。


清单 2. 工厂类 WBEMClientFactory 初始化 WBEMClient 示例
 /** 
 * Initializes a cim client connection to a given CIMOM. Note that the 
 * initialization will not lead to client<->CIMOM communication, the first 
 * request will be sent to the CIMOM when the first operation is executed. 
 * 
 * @param pWbemUrl The URL of the WBEM service (e.g. 
 *      <code>https://myhost.mydomain.com:5989</code>) 
 * @param pUser The user name for authenticating with the WBEM service 
 * @param pPassword The corresponding password 
 * @return A <code>WBEMClient</code> instance if connect was successful, 
 *     <code>null</code> otherwise 
 */ 
 public static WBEMClient connect(final URL pWbemUrl, final String pUser, 
  final String pPassword) { 
 try { 
 final WBEMClient client = WBEMClientFactory.getClient("CIM-XML"); 
 final CIMObjectPath path = new CIMObjectPath( 
  pWbemUrl.getProtocol(), pWbemUrl.getHost(), String 
  .valueOf(pWbemUrl.getPort()), null, null, null); 
 final Subject subject = new Subject(); 
 subject.getPrincipals().add(new UserPrincipal(pUser)); 
 subject.getPrivateCredentials().add( 
  new PasswordCredential(pPassword)); 
 client.initialize(path, subject, new Locale[] { Locale.US }); 
 
 return client; 
 } catch (final Exception e) { 
 e.printStackTrace(); 
 } 
 return null; 
 } 

用户在初始化 WBEMClient 的同时还能对其进行配置,配置一些协议相关的特殊属性,这些属性将影响客户端在运行时的行为。比方说,某些客户端协议在操作过程中支持超时选项,这时用户对 WBEMClient 的配置代码就可以做如下修改。


清单 3. 配置 WBEMClient 客户端的连接属性
 // Set http timeout property with the value 3 minutes 
 client.setProperty(WBEMConfigurationProperties.HTTP_TIMEOUT, String 
  .valueOf(180 * 1000)); 

所有的客户端属性都能在运行时配置,接口 WBEMConfigurationProperties 包含了每一个客户端属性所对应的常量值。客户端配置通常包括三种情况,全局配置,客户配置,局部配置,下面将对这三种情况做一个简单的介绍。

全局配置:全局配置的涉及范围是整个 Java 虚拟机,大部分属性可以通过方法 System.setProperty() 进行设置,还有一些可以通过 Security.setProperty() 进行设置,当然用户也可以通过装载配置文件来设置全局属性;

客户配置:对于单独的 WBEMClient 客户端,全局配置是有可能被覆盖的。这些设置在所有使用该客户端的线程中共享,用户可以调用 WBEMClient.setProperty() 来覆写这些属性。请注意,大部分属性设置代码如果是在 initialize() 方法以后被调用的,那么它们将不起任何作用;

局部配置:假使你有很多线程同时共享一个 WBEMClient 客户端,并且你想修改其中一个线程实例的配置,而不影响其他线程,那么你就需要使用局部配置。它将覆盖全局配置和客户配置,但是只局限于当前线程。你需要将 WBEMClient 转型成 WBEMClientSBLIM,然后调用方法 setLocalProperty() 来设置局部属性。

处理查询结果 CloseableIterator

和 SBLIM Client 1.x 使用 java.util.Enumeration 返回查询结果不一样的是,2.x 使用了继承于 java.util.Iterator,自己封装的接口 javax.wbem.CloseableIterator 来封装返回结果。从 Java5 开始 Iterator 代替 Enumeration,所以对于支持 Java5 的 SBLIM Client 2.x 来说,用 Iterator 代替 Enumeration 是必然的趋势。不仅如此,SBLIM Client 2.x 对 Iterator 进行了扩展,增加了两个新的方法:

close() 方法:用于释放 iterator 使用的资源,所以每次在解析查询结果使用完毕之后,应该显式地调用 close() 方法。

getWBEMException() 方法:用于获取解析过程中出现的异常信息。解析过程在用户调用 next() 或者 hasNext() 接口之前就已经完成,CloseableIterator 的实现类在解析的时候遇到 Exception 的时候会把 Exception 信息存储起来,供用户通过 getWBEMException() 方法获取。


清单 4. CloseableIterator 接口
 public interface CloseableIterator extends Iterator<Object> { 
 
 /** 
 * Closes the <code>Iterator</code>. This allows the underlying 
 * implementation to do any cleanup and disconnect from any source  
 * that it may be using. 
 */ 
 public void close(); 
 
 /** 
 * If next() or hasNext() throws a RuntimeException, this method must  
 * be called to get the WBEMException. 
 * 
 * @return The WBEMException or null if one was not thrown. 
 */ 
 public WBEMException getWBEMException(); 
 
 } 

应用程序中使用 CloseableIterator 的方法和 Iterator 的区别就在于这两个新增加的方法。close() 方法是建议在每个 CloseableIterator 使用完毕后调用。而 getWBEMException() 方法则是在需要对 CloseableIterator 的操作捕获到的异常信息进行操作的时候调用。下面这段代码展示了如何 CloseableIterator 处理查询结果,以及如何适当使用新增加的这两个方法。


清单 5. 对 CloseableIterator 进行处理获得查询结果
 //Call WBEMClient.enumerateInstances( … ) to get CloseableIterator of result. 
 CloseableIterator iter = client.enumerateInstances( 
  new CIMObjectPath("CIM_ComputerSystem",nameSpace), true, false, false, null); 
 
 Vector<CIM_ComputerSystem> result = new Vector<CIM_ComputerSystem>(); 
 try{ 
 if (iter != null) { 
    while (iter.hasNext()) { 
      //Take your action to the element from next(). 
      Object next = iter.next(); 
      if(next instanceof CIMInstance) 
      result.add(new CIM_ComputerSystem(client, (CIMInstance)next)); 
    } 
  } 
 }catch(Exception e){ 
   //Throw the WBEMException. 
   throw iter.getWBEMException(); 
 }finally{ 
   //Make sure CloseableIterator.close() is called. 
   iter.close(); 
 }  

至此,用户应该已经可以从 CloseableIterator 中获取查询结果,根据需要自行处理,并且准确地获取和处理可能的异常情况,以及及时释放 iterator 占用的系统资源。如果忘记执行 close() 方法,很可能会造成网络 socket 的泄露。

警惕 Object Path 陷阱

Object Path 被用来作为每个 CIM 对象 / 类 / 命名空间的唯一标识,类似于 web 资源的 URL。其完整形式是 [scheme:host:port//]namespace:[classname[.key=value]*],其中 scheme:host:port 是可选部分。如果我们把 host 信息作为唯一标识的一部分,就会带来一些的问题。举例来说,我们通过 EnumerateInstances() 方法得到的某个 instance 可能带有 host 信息,而通过 associators() 方法得到的 instance 就不带有 host 信息,代码如下,程序执行抛出 RuntimeException:


清单 6. 通过 EnumerateInstances() 和 associators() 查询 cim_computersystem 实例
 // 取出所有 cim_computersystem 实例 
 instances = client.enumerateInstances( 
   new CIMObjectPath("cim_computersystem", "root/aristos"), true, false, false, null); 
 
 while(instances.hasNext()){ 
  CIMInstance instance = (CIMInstance)instances.next(); 
  csMap.put(instance.getObjectPath().toString(), instance); 
 } 
     
 // 通过关联 CIM_ElementConformsToProfile,找出 //cim_computersystem 实例 
 instances = client.associators(profile.getObjectPath(), 
          "CIM_ElementConformsToProfile", "cim_computersystem",  
 null, null, false, false, null); 
     
 if(instances.hasNext()){ 
  CIMInstance instance = (CIMInstance)instances.next(); 
  //~ 未找到,抛错 
  if(null == csMap.get(instance.getObjectPath().toString())){ 
    throw new RuntimeException(); 
  } 
  else{ 
    System.out.println("Associated cim_computersystem is found!"); 
  } 
 } 

在 SBLIM Client 的文档中,可以找到 WBEMClient 操作及其是否返回 Host 信息的对应关系,摘录部分如下:


表 1. SBLIM Client 操作及结果是否包含 Host 信息

Operation Host
EnumerateClassNames No
EnumerateClasses No
GetClass No
EnumerateInstanceNames No
EnumerateInstances No
GetInstance No
AssociatorNames Maybe
Associators Yes
ReferenceNames Maybe
References Yes

为了避免上述情况的出现,我们可以对每个得到的 Object Path 对象,手动的去除 host 信息。示例代码如下:


清单 7. 提取 Object Path 中 host 信息
 CIMObjectPath _cop = cop; 
 
 if (null != cop) { 
 // 去除可选的 host 信息 
 _cop = new CIMObjectPath(cop.getObjectName(), cop.getNamespace(), cop.getKeys()); 
 } 
 
 return _cop; 

将经过处理的 Object Path 放入清单 1 中,程序将能从 csMap 中取出相关实例并打印提示信息"Associated cim_computersystem is found!"

处理 CIM 事件

SBLIM 2.x 接收事件的机制和 1.x 大致相同,主要的改动是调整了 indication 的接口,包装了一些实现细节。如果要把使用 1.x 的代码迁移到 2.x 上面,我们需要对注册和管理 Listener 的代码做一些改动。SBLIM 接收事件的机制如下图:


图 2. SBLIM 事件接收机制
SBLIM Client 2 之最佳实践

查看原图(大图)

在 1.x 版本的时候,如果要通过 SBLIM 去监听事件, 我们需要手动创建 HTTP Server connection, Indication Handler 和 Event Dispathcher 来注册,例子如下:


清单 8. SBLIM Client 1.x 中注册 Listener 示例
 private HttpServerConnection startIndicationListener(int port, boolean ssl) { 
  //create a Listener list 
  CIMIndicationListenertList listeners = new CIMIndicationListenertList(); 
 
  //add a listener into the list 
  listeners.addListener(new CIMListener() { 
    public void indicationOccured(CIMEvent pEvent) { 
      System.out.println("Received indication from " + 
                pEvent.getInetAddress() + 
                " on local path" + pEvent.getID()); 
      System.out.println("Indication instance: " + 
                pEvent.getIndication()); 
    } 
  }); 
 
  //create a event dispatcher 
  CIMEventDispatcher dispatcher = new CIMEventDispatcher(listeners); 
 
  //create a event handler 
  CIMIndicationHandler handler = new CIMIndicationHandler(dispatcher); 
 
  try { 
    //create a http server connection to receive event 
    HttpServerConnection server = 
        new HttpServerConnection(new HttpConnectionHandler(handler), port, ssl); 
 
    server.setName("CIMListener - Http Server"); 
    server.start(); 
 
    return server; 
  } catch (Exception e) { 
    dispatcher.kill(); 
  } 
 
  return null; 
 } 

在 2.x 版本中,SBLIM 调整了 indication 的接口,隐藏了 Event Dispatcher 和 Event Handler 等实现细节, 使用者只需要调用 WBEMListener 的 addListener 去注册 listener, 比以前要简化很多,例子如下 :


清单 9. SBLIM Client 2.x 中注册 Listener 示例
 private WBEMListener startIndicationListener(int pPort, boolean pSsl) 
                     throws IOException { 
  try { 
    // Get a WBEM Listener implementation for the specified protocol. 
    WBEMListener listener = WBEMListenerFactory.getListener("CIM-XML"); 
 
    //Add a new listener using the specified port 
    listener.addListener(new IndicationListener() { 
      public void indicationOccured(String pIndicationURL, 
                     CIMInstance pIndication) { 
          System.out.println("Indication received on: " + 
                   pIndicationURL + ":"); 
          System.out.println(pIndication.toString()); 
      } 
    }, pPort, pSsl ? "https" : "http"); 
 
    return listener; 
  } catch (IOException e) { 
    System.err.println("Failed to bind socket to port " + pPort); 
    throw e; 
  } 
 } 

从以上的 sample code 也可以看出 SBLIM1.x 和 2.x 对 Listener 的管理也有了很大改动 , 2.x 隐藏了对 HttpServerConnection 等类的调用,我们不需要再关注 http connection 的维护,同时 2.x 移除了 CIMIndicationListenerList, 简化了 CIMEventDispatcher, 开放了对 listener 的处理。

在 1.x 版本里,CIMEventDispatcher 拥有一个 CIMIndicationListenertList 实例,注册 listener 时,我们需要在 provider 上将 CIM_ListenerDestinationCIMXML 的 destination 设置成"protocol://ip_address:port/hashcode"的形式,这样在收到 event 时,CIMEventDispatcher 会根据 hashcode 从 CIMIndicationListenertList 中找出对应的 listener 并将 event 传给 listener。使用者通过 CIMIndicationListenerList 来管理 listener。 这其中,Event 与 Listener 的对应关系由 SBLIM 来管理(根据 listener 实例的 hashcode).

而在 2.x 中,WBEMListener.addListener 方法隐藏了对 CIMEventDispatcher 和 CIMIndicationHandler,HttpServerConnectionserver 等的调用,并且在一个端口上,用户只能注册一个 CIMListener. 所以当有多个 Listener 需要在同一个接口监听的时候,我们需要写一个 实现 IndicationListener 接口的类去接收这些 Listener,并在 indicationOccured 里面根据参数去判断应该将 event 传给哪一个 listener,这样我们可以在自己代码中设定 event 与 listener 的关系。以下代码可供参考:


清单 10. 集中处理 CIM 事件代码示例
… 
 WBEMListener listener = WBEMListenerFactory.getListener("CIM-XML"); 
 listener.addListener(_internalListener,pPort,”http”); 
 … . 
  
 private class InternalListener implements IndicationListener{ 
 HashMap<Integer, CIMListener> managedListeners = 
                   new HashMap<Integer, CIMListener>(); 
   
 void addListener(CIMListener listener){ 
  managedListeners.put(Integer.valueOf(listener.hashCode()), listener); 
 } 
   
 void removeListener(CIMListener listener){ 
  managedListeners.remove(Integer.valueOf(listener.hashCode())); 
 } 
   
 /* (non-Javadoc) 
 * @see javax.wbem.listener.IndicationListener 
 *   #indicationOccured(java.lang.String, * javax.cim.CIMInstance)  
 */ 
 public void indicationOccured(String pIndicationURL, CIMInstance pIndication) { 
 String hashcode = null; 
    
 if(null != pIndicationURL){ 
   int pos = pIndicationURL.lastIndexOf("/"); 
     
   if(pos < 0){ 
     hashcode = pIndicationURL; 
   }else{ 
     hashcode = pIndicationURL.substring(pos); 
   } 
     
   CIMListener listener = managedListeners.get(Integer.valueOf(hashcode)); 
     
   if(null != listener){ 
     listener.indicationOccured(new CIMEvent(pIndication)); 
   } 
 } 
 }  
 } 

总结

本文通过实例,详尽描述了 SBLIM CIM Client 2.x 新版本中的一些特性,包括配置和初始化客户端,处理查询结果 CloseableIterator,使用 CIM Object Path 过程中的某些陷阱,以及如何处理 CIM 事件等,同时作者分享了在使用这些特性过程中的一些经验所得,结合具体的实际代码,希望能给读者对于 SBLIM CIM Client 2.x 新版本的使用带来全方位的认识,也希望此文能给读者的日常开发工作,尤其是对 CIM Client 程序的开发带来帮助。

Tags:SBLIM Client 最佳

编辑录入:爽爽 [复制链接] [打 印]
赞助商链接