SBLIM Client 2 之最佳实践
2009-12-18 00:00:00 来源:WEB开发网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 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 事件接收机制
查看原图(大图)
在 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 程序的开发带来帮助。
- ››最佳ASP.NET编程习惯
- ››Client does not support authentication protoco...
- ››最佳 iPhone 游戏奖:《蜘蛛:布莱庄园的秘密》介...
- ››SBLIM Client 2 之最佳实践
- ››最佳的75个网络安全工具
- ››最佳措施 全面解决MySQL网络安全问题
- ››最佳J2EE方案讨论之O-R Mapping
- ››最佳实践:有状态会话 bean运行结束时应及时被显式...
- ››最佳实践:勿在 Servlet 中实现 SingleThreadMode...
- ››最佳实践:避免或最小化 Servlet 中的同步
- ››最佳SQL基础
- ››最佳做法检查表(SQL Server官方推荐安全配置方案)...
更多精彩
赞助商链接