IBM®
跳转到主要内容
    中国 [选择]    使用条款
 
 
Select a scope: Search for:    
    首页    产品    服务与解决方案     支持与下载    个性化服务    
跳转到主要内容

developerWorks 中国  >  Rational  >

使用 HTTP 网络技术构建灵活的 Rational ClearCase系统,第 2 部分: 用 Web Service 查询 RationalClearcase 中的 UCM 项目信息

使用 Java 语言和 Web Service 技术开发 Rational Clearcase UCM 项目信息查询系统

developerWorks
文档选项

未显示需要 JavaScript 的文档选项


级别: 中级

王 颖初, 软件工程师, IBM

2009 年 10 月 23 日

Rational Clearcase 是一套功能全面的软件配置管理(Software Configuration Management,SCM)解决方案。但是 ClearCase 的系统安装与运行维护管理非常复杂,很多系统运行相关的功能、状态监控任务以及 UCM 项目状态查询操作都需要通过系统管理员在服务器端执行复杂的基于命令行的 ClearCase 指令。这一过程非常复杂繁琐,也无法供 ClearCase 普通用户使用。

本系列文章共有两篇,以一个实际生产环境中使用的 ClearCase 监控系统的开发为例,向读者介绍如何使用基于 Java 语言的 Web2.0 和 Web Service 技术来开发一个 Linux 环境下的 ClearCase 监控和 UCM 项目查询系统。本文是该系列的第二部分,将向您演示如何使用 Java 语言和 Web Service 技术来开发一个 Rational Clearcase UCM 项目信息查询系统。

UCM 项目信息查询系统应用场景介绍

在 Clearcase UCM 模型中包含了很多的记录项目开发过程和进度变化的信息,例如项目的每个子开发流中包含了各个分支的开发状态信息,每个基线中包含了各个不同时刻的项目开发活动信息等。这些信息对软件项目开发的各个环节都有很大的帮助。例如在软件产品的构建过程中可以通过查询 UCM 活动变更集来获取每次产品构建的代码变更状况,在项目进度追踪过程中可以通过查询各个 UCM 组件的基线来获取不同时间点上项目开发的进度状况。在现有的 Clearcase 客户端程序 (CCRC,CC Web 和 CC Native Client 等 ) 中,只提供了非常有限的基于 UI 操作的 UCM 项目信息查询功能(例如可以通过鼠标点击的方式查询特定基线中包含的变更集等功能)。这些功能无法满足软件开发中各个环节的需求,同时现有客户端没有提供这些功能的计算机程序编程接口,开发流程中使用的其他软件(例如产品构建程序)无法从这些已有的客户端程序中获取 UCM 信息。

在常规的方式下 , 为了获取这些 UCM 信息,软件开发项目组织需要编写自定义的脚本程序 ( 这些脚本程序通常由 Perl 和 Shell 语言编写,包含了查询所需 UCM 信息的 cleartool 命令行指令 ) 并按照需要在 Clearcase 服务器主机上运行这些程序 , 之后需要以某种方式从服务器主机中下载执行结果并解析所需 UCM 信息。另一种方法是使用 Clearcase 提供的 Rational Team API 编写基于 Java 语言的自定义客户端程序来查询所需 UCM 信息,但是 Rational Team API 只能在已安装了 Clearcase 客户端的计算机中运行,同时只能由 Java 语言编写的程序调用。上述两种方法需要结合使用多种不同的编程语言,或者有一定的使用局限性。并且他们都涉及到多台不同物理机器的交互以及多个不同角色的项目成员的协同工作。过程非常的复杂,并且没有很好的可维护性和功能可扩展性。

而在使用本文所述的 UCM 项目信息查询系统时,软件项目开发团队只需要在各种项目开发支持软件中使用由任意语言编写的 Web Service 客户端程序来访问 UCM 查询系统即可获取所需的项目 UCM 信息。这一过程不需要安装 Clearcase 客户端程序,也无需多个不同角色的项目成员的协同工作。开发、使用的过程非常简便,同时能够使软件开发团队以一种统一的方式在各种异构的软件系统中获取到 Clearcase 服务器中的 UCM 项目状态信息。

图 1 是使用上述两种方式的系统结构对比。在本文的以下部分中首先会介绍 Clearcase UCM 的基本概念和相关的命令行查询指令,之后将介绍如何使用 Java 和 Web Service 来开发通用的 UCM 项目信息查询系统,这一查询系统能够以 SOAP Web Service 的方式向客户端程序提供 Clearcase UCM 查询服务。


图 1. 常规方式和使用 UCM 项目信息查询系统方式的结构对比
常规方式和使用 UCM 项目信息查询系统方式的结构对比




回页首


Rational ClearCase UCM 的基础概念

UCM (Unified Changed Management), 意为统一变更管理模式,是管理软件开发过程中所有变更的 " 最佳实践 "。它定义了一个可以立即用于软件开发项目的一致并基于活动的变更管理流程。 UCM 通过抽象层次的提升简化了软件开发,从而使得软件开发团队能够从更高的层次根据活动(activity)来管理变更。通过 UCM,一个开发活动可以自动地同其变更集(changeset, 封装了所有用于实现该活动的项目工件)相关联。以下是 UCM 中常用对象的简要介绍:

UCM 和 RUP

通过结合使用Rational ClearCase和ClearQuest,UCM已成为Rational软件开发最佳实践的全面框架 - Rational统一过程(RUP)的关键组成部分。

关于UCM的详细概念和介绍,请参考“第三代配置管理解决方案: 统一变更管理(UCM)”一文,和教程“Rational统一变更流程UCM”。

关于RUP的详细概念和介绍,请参考资源“RUP 学堂”。

  • 项目(Project):包含了软件产品开发的配置管理所需要的 UCM 元数据和一些配置信息,例如 Component、Baseline,Stream 等。
  • Project VOB(PVOB):存储 UCM 所需的一些特殊的信息(如 Proejct,Stream,Activity 及 Change Set 等)的 VOB,UCM Project 的信息必须保存在 PVOB 中。
  • 构件(Component):软件开发项目中的代码、文档等按一定的目录结构组织而成的可重用的工件集合。Component 与 Project 相关联,Project 管理的所有的对象都从属于某一个特定的 Component ,每个 Project 至少有一个 Component。
  • 开发流(Development Stream):为每个开发人员准备的一个独立的开发环境,包含了在这个开发流上的 Activity 与修改的配置项的版本信息,UCM 通过开发流简化了并行开发的配置管理工作。
  • 集成流(Integration Stream):代表项目开发的代码主干,每个开发流都是集成流的一个分支,开发人员在开发流上完成工作后,再将工作结果提交到集成流中,每个 Project 都有一个 Integration Stream。
  • 基线(Baseline):基线是一个 Component 在某一特定时刻的快照。它包括在此时这一 Component 中所有对象的版本信息集合。当配置一个新工作流时,基线被用来指定哪些版本将被选在此流中。
  • 活动(Activity):UCM 模式中的一个关键概念,跟踪完成一项开发任务所引起的所有配置项的变更。在 UCM 模式下为了完成一项开发任务所进行的所有会引起配置项发生变化的操作(Check Out、Check In、Add to Source Control 等)都必须关联到一个 Activity。这个 Activity 会使用一个变更集 (Change Set) 来记录这些版本变更。
  • 变更集(Change Set):记录了 Activity 所关联的所有的配置项的版本变更,每个 Activity 都有一个 Change Set。

清单 1. UCM 对象的层次关系
 vob1 vob2 
 / | \ / \
 目录 1 目录 2 目录 3 目录 4 目录 5
 | | | | |
 PVOB ------------------>comp1 comp2 comp3 comp4 comp5 
 / \ | | | | |
project1 project2 ----------->|______|_______|_______|______|_______ 无根构件 NRcomp6
 / \ 
 stream1 stream2
 | | 
 | -deliver---> |





回页首


Rational ClearCase 中常用的 UCM 查询指令

Clearcase 中内置的命令行工具 cleartool(/opt/rational/clearcase/bin/cleartool) 中包含有很多子命令可以用来进行 UCM 相关的操作 , 下面简要的介绍其中常用的一些查询和比较命令。

Project 查询命令 lsproject

lsproject 命令可以用来查询一个指定的 pvob 中所有 project 的信息,也可以根据某一具体的 project 名称来查询该 project 的详细信息。例如 :

  • cleartool lsproject -invob /vob_tag/project_pvob :列出 pvob /vob_tag/project_pvob 中包含的所有项目信息。
  • cleartool lsproject -l userProject@/vob_tag/project_pvob :显示 pvob /vob_tag/project_pvob 中项目 userProject 的详细信息。
  • cleartool lsproject -tree userProject@/vob_tag/project_pvob :以树型层次结构显示 project userProject 中包含的所有子对象(stream,activity 等)。

Stream 查询命令 lsstream

lsstream 命令可以用来查询一个 UCM project 中的 Stream 信息。示例如下:

  • cleartool lsstream -in userProject@/vob_tag/project_pvob :列表显示 UCM project userProject 中的所有 Integration Stream。
  • cleartool lsstream -l userProjectStream_Integration@/vob_tag/project_pvob :显示 stream userProjectStream_Integration 的详细信息。
  • cleartool lsstream -tree userProjectStream_Integration@/vob_tag/project_pvob :以树型层次结构显示 Stream userProjectStream_Integration 中包含的所有子对象(子 stream,activity 等)。

Baseline 查询命令 lsbl

lsbl 命令是 UCM 查询中非常常用的指令,它可以用来查询一个指定的 UCM Stream 中包含的 Baseline 的详细信息 :

  • cleartool lsbl -stream userProjectStream_Integration@/vob_tag/project_pvob : 列表显示 UCM stream userProjectStream_Integration 中的所有 baseline。
  • cleartool lsbl -component projectComponentA@/vob_tag/project_pvob/ -stream userProjectStream_Integration@/vob_tag/project_pvob : 列表显示 UCM stream userProjectStream_Integrationcomponent projectComponentA 中的所有 baseline。
  • cleartool lsbl -tree -stream userProjectStream_Integration@/vob_tag/project_pvob : 以树型层次结构显示 stream userProjectStream_Integration 中的所有 baseline。
  • cleartool lsbl -l projectIntegrationStream_BaselineA@/vob_tag/project_pvob : 显示 baseline projectIntegrationStream_BaselineA 的详细信息。
  • cleartool lsbl -tree projectIntegrationStream_BaselineA@/vob_tag/project_pvob : 以树型层次结构显示 baseline projectIntegrationStream_BaselineA 中包含的所有子对象。

Baseline 比较命令 diffbl

diffbl 命令的主要用途是比较两个不同的 baseline 或 stream 中 activity 的变化情况 , 它可以用来追踪项目开发中的代码变更 , 示例如下 :

  • cleartool diffbl -predecessor baseline:projectIntegrationStream_BaselineA@/vob_tag/project_pvob : 显示 baseline projectIntegrationStream_BaselineA 与它前一个 baseline 之间的 Activity 变化。
  • cleartool diffbl baseline:projectIntegrationStream_BaselineA@/vob_tag/project_pvob baseline:projectIntegrationStream_BaselineB@/vob_tag/project_pvob : 显示 baseline projectIntegrationStream_BaselineA projectIntegrationStream_BaselineB 之间的 Activity 变化。
  • cleartool diffbl stream:userProjectStream_Integration@/vob_tag/project_pvob stream:userProjectStream_DeveloperA@/vob_tag/project_pvob : 显示 stream userProjectStream_Integration userProjectStream_DeveloperA 之间的 Activity 变化。

Activity 和 Change Set 查询命令 lsactivity

在 ClearCase UCM 方式下 , 针对某一特定任务进行的所有会引起版本变化的操作都与一个指定的 activity 相关联在一起,所有的对象版本变化信息都会被记录在这个 activity 的 change set 中,clearcase 使用命令 lsactivity 来显示这些信息。示例清单 2 是命令:cleartool lsactivity -l Check_in_license_files_for_toolkit@/vob_tag/project_pvob 的输出结果,它显示了 activity Check_in_license_files_for_toolkit 的信息和其中包含的 change set。


清单 2. lsactivity 命令输出结果
activity "Check_in_license_files_for_toolkit"
 2008-03-25T11:22:42+08:00 by UI Dev (uidev.clearusers@UIDevSUPPORT01)
 master replica: csdl.project_pvob@/vob_tag/project_pvob/
 owner: ccadmin
 group: projectdev
 stream: UI_DEV@/vob_tag/project_pvob/
 title: Check in license files for toolkit
 change set versions:
 /vob_tag/project_src/Tools/License/LI_zh@@/main/userProjectStream_Integration/UI_DEV/1
 /vob_tag/project_src/Tools/License/LI_tr@@/main/userProjectStream_Integration/UI_DEV/1
 /vob_tag/project_src/Tools/License/LI_pt@@/main/userProjectStream_Integration/UI_DEV/1
 /vob_tag/project_src/Tools/License/LI_pl@@/main/userProjectStream_Integration/UI_DEV/1
 /vob_tag/project_src/Tools/License/LI_ko@@/main/userProjectStream_Integration/UI_DEV/1
 /vob_tag/project_src/Tools/License/LI_ja@@/main/userProjectStream_Integration/UI_DEV/1





回页首


使用 Java 和 Web Service 技术开发 Clearcase UCM 查询程序

本文所述 Clearcase UCM 查询程序是一个运行在 Clearcase 服务器主机中的轻量级 Web Service 程序 , 它包含一个 SOAP Web Service 服务组件和一个本系列文章第一部分中介绍过的操作系统进程调用组件,用户通过 HTTP 对 UCM 查询程序进行 SOAP 式的查询方法调用。当 UCM 查询程序接收到用户请求后,Web Service 服务组件会将这个方法调用转换为 cleartool 命令,并使用操作系统进程调用组件执行这一命令,最后 Web Service 服务组件会将执行结果转化为 SOAP 对象并将它返回给用户。图 2 是本文所述 Clearcase UCM 查询程序的基本结构。


图 2. Rational Clearcase UCM 查询程序基本结构图
Rational Clearcase UCM 查询程序基本结构图

操作系统进程调用组件的详细介绍请参考本系列文章的第一部分,本文将着重介绍 SOAP Web Service 服务组件部分。

SOAP Web Service 服务组件

SOAP Web Service 服务组件逻辑上由 UCM cleartool 查询程序和 SOAP server 程序两部分组成。SOAP server 程序接收用户调用请求,并将调用请求转发到 UCM cleartool 查询程序中,查询程序会根据调用请求的操作名称和参数生成 cleartool 命令并使用操作系统进程调用组件执行这些 cleartool 命令。当 cleartool 命令完成后,查询程序会根据需要将命令的返回结果解析、封装成 JavaBean 对象,并将这些对象传递回 SOAP server 程序中。SOAP server 程序会将这些 JavaBean 转化为 SOAP 可识别的对象并作为用户调用请求的结果返回给用户端程序。图 3 为 SOAP Web Service 服务组件的结构图。


图 3. SOAP Web Service 服务组件结构图
SOAP Web Service 服务组件结构图

UCM cleartool 查询程序

UCM cleartool 查询程序使用 Java 语言开发 , 程序类位于包 com.cn.ibm.csdl.ecm.build.nerveCell.util.clearcaseUCMQuery 中 , 由查询工具类 ClearcaseUCMQueryUtil 和一系列代表 UCM 对象数据的 JavaBean 组成。当用户发起某一 UCM 查询操作时 , SOAP server 程序调用 ClearcaseUCMQueryUtil 中的相应 Java 方法。ClearcaseUCMQueryUtil 首先根据输入参数生成 cleartool 命令 , 之后使用操作系统进程调用组件执行该命令并在命令完成后解析返回数据 , 最后将解析后的数据使用 UCM 对象数据 JavaBean 封装并返回给 SOAP server 程序。清单 3 是 ClearcaseUCMQueryUtil 中的 getCompareBaselineObjWithPredecessor 方法 , 它比较指定的 baseline 和前一个基线的版本变化 , 并将结果封装在 BaselineCompareInfoObj JavaBean 中。ClearcaseUCMQueryUtil 中的其他的方法请参考本文附件中的 eclipse 项目工程。


清单 3. getCompareBaselineObjWithPredecessor 方法
public static BaselineCompareInfoObj getCompareBaselineObjWithPredecessor
(String baselineName,String vobName){
String cleartoolPath="/opt/rational/clearcase/bin/cleartool";
String commandString=
cleartoolPath+" diffbl -predecessor baseline:"+baselineName+"@"+vobName;
java.util.List resultList=runSingelRemoteCommand(commandString);
if(resultList==null){return null;}
String returnBaselineInfoHead=(String)resultList.get(0);
if(!returnBaselineInfoHead.startsWith("Comparing the following:")){return null;}
BaselineCompareInfoObj currentBaselineCompareInfoObj=new BaselineCompareInfoObj();
int differencesStart=resultList.indexOf("Differences:")+1;
String[] baselinesArray=new String[2];
baselinesArray[0]=(String)resultList.get(1);
baselinesArray[1]=(String)resultList.get(2);
currentBaselineCompareInfoObj.setBaselines(baselinesArray);
int differencesNum=resultList.size()-differencesStart;
String[] dArray=new String[differencesNum];
for(int i=0;i<differencesNum;i++){
dArray[i]=(String)resultList.get(i+differencesStart);
}
currentBaselineCompareInfoObj.setDifferences(dArray);
return currentBaselineCompareInfoObj;
}

SOAP server 程序

本程序的 Web Service 服务组件使用 Java 6 的 JAX-WS2.0 特性所提供的 SOAP 功能 ( 使用了 cxf 和 Jetty 作为后端类库 ) 。使用这一方式可以在 Java6 标准版的环境下提供 Web Service 支持而不需要将程序部署在 JavaEE 服务器上,从而可以简化程序的开发难度并降低 Clearcase 主机的负载。使用 JAX-WS2.0 提供 SOAP 服务需要使用 3 个 Java 类 : 一个服务定义接口,一个服务实现类和一个服务发布类。在 UCM 查询程序中这些类位于 com.cn.ibm.csdl.ecm.build.nerveCell.util.clearcaseUCMQuery.webservice 包中,以下是它们的说明 :

ClearcaseUCMQueryService: 服务定义接口 , 在这个 interface 中定义了每一个将要发布的 SOAP Web Service UCM 查询操作 , 清单 4 中的代码片断显示了该接口中操作 getCompareBaselineObjWithPredecessor 的定义。


清单 4. getCompareBaselineObjWithPredecessor 操作的服务接口定义
package com.cn.ibm.csdl.ecm.build.nerveCell.util.clearcaseUCMQuery.webservice; 
import javax.jws.WebService;
import com.cn.ibm.csdl.ecm.build.nerveCell.util.clearcaseUCMQuery.BaselineCompareInfoObj;
......
@WebService
public interface ClearcaseUCMQueryService { 
...... 
BaselineCompareInfoObj getCompareBaselineObjWithPredecessor
 (String baselineName,String vobName); 
......
}

ClearcaseUCMQueryServiceImpl: 服务实现类 , 这个类通过使用 UCM cleartool 查询程序为 ClearcaseUCMQueryService 定义的每个方法提供了一个实现。清单 5 中的代码片断显示了该类中方法 getCompareBaselineObjWithPredecessor 的实现。


清单 5. getCompareBaselineObjWithPredecessor 操作的服务实现类
package com.cn.ibm.csdl.ecm.build.nerveCell.util.clearcaseUCMQuery.webservice;
import javax.jws.WebService;
import com.cn.ibm.csdl.ecm.build.nerveCell.util.clearcaseUCMQuery.BaselineCompareInfoObj;
import com.cn.ibm.csdl.ecm.build.nerveCell.util.clearcaseUCMQuery.ClearcaseUCMQueryUtil;
......
@WebService(endpointInterface = 
 "com.cn.ibm.csdl.ecm.build.nerveCell.util.clearcaseUCMQuery.
 webservice.ClearcaseUCMQueryService", 
 serviceName = "ClearcaseUCMQueryService")
public class ClearcaseUCMQueryServiceImpl implements ClearcaseUCMQueryService{
......
public BaselineCompareInfoObj getCompareBaselineObjWithPredecessor
 (String baselineName, String vobName) {
 return ClearcaseUCMQueryUtil.getCompareBaselineObjWithPredecessor
 (baselineName, vobName);
}
......
}
}

ClearcaseUCMQueryServicePublisher: 服务发布类 , 这个类将 ClearcaseUCMQueryService 和 ClearcaseUCMQueryServiceImpl 耦合在一起并发布 UCM 查询的 SOAP Web Service 服务。


清单 6. ClearcaseUCMQueryServicePublisher 类定义
package com.cn.ibm.csdl.ecm.build.nerveCell.util.clearcaseUCMQuery.webservice;
import javax.xml.ws.Endpoint;
public class ClearcaseUCMQueryServicePublisher{
 public static void main(String args[]) throws Exception {
 new ClearcaseUCMQueryServicePublisher().
 publishService("http://localhost:9101/ccUCMQueryService"); 
 } 
 public void publishService(String serviceAddress){
 ClearcaseUCMQueryServiceImpl implementor = new ClearcaseUCMQueryServiceImpl();
 String address = serviceAddress.trim();
 Endpoint.publish(address, implementor);
 System.out.println("Published Service at :"+serviceAddress); 
 }
}

当 ClearcaseUCMQueryServicePublisher 中的 main 方法运行后,用户就可以在地址 http://hostname:9101/ccUCMQueryService 上执行 UCM 查询程序中的所有 SOAP 查询操作。通过在 Web 浏览器中访问地址 http://localhost:9101/ccUCMQueryService?wsdl 可以获得这个服务的 WSDL 定义。





回页首


从客户端访问执行 Rational Clearcase UCM 查询程序

本文所述的 Clearcase UCM 查询程序可以接受任何编程语言实现的 SOAP Web Service 客户端程序的访问。下面以 Java 程序实现的一个客户端 ClearcaseUCMQueryClient 为例演示这一过程。


清单 7. ClearcaseUCMQueryServicePublisher 类定义
package com.cn.ibm.csdl.ecm.build.nerveCell.util.clearcaseUCMQuery.webservice;
import javax.xml.namespace.QName;
import javax.xml.ws.Service;
import javax.xml.ws.soap.SOAPBinding;
import com.cn.ibm.csdl.ecm.build.nerveCell.util.clearcaseUCMQuery.BaselineCompareInfoObj;
......
public class ClearcaseUCMQueryClient {
 private static final QName SERVICE_NAME = 
 new QName("http://webservice.clearcaseUCMQuery.util.nerveCell.build.
 ecm.csdl.ibm.cn.com/", "ClearcaseUCMQueryService");
 private static final QName PORT_NAME = 
 new QName("http://webservice.clearcaseUCMQuery.util.nerveCell.build.
 ecm.csdl.ibm.cn.com/", "ClearcaseUCMQueryServicePort");
 private static ClearcaseUCMQueryService getClearcaseUCMQueryService
 (String commandHostAddress){
 Service service = Service.create(SERVICE_NAME); 
 String endpointAddress = commandHostAddress.trim();
 service.addPort(PORT_NAME, SOAPBinding.SOAP11HTTP_BINDING, endpointAddress);
 ClearcaseUCMQueryService ccQueryService=
 service.getPort(ClearcaseUCMQueryService.class);
 return ccQueryService; 
 }
 public static BaselineCompareInfoObj getCompareBaselineObjWithPredecessor
 (String commandHostAddressString,String baselineName,String vobName){
 ClearcaseUCMQueryService ccQueryService=
 getClearcaseUCMQueryService(commandHostAddress);
 return ccQueryService.getCompareBaselineObjWithPredecessor(baselineName, vobName);
}
......
 public static void main(String[] args){
 System.out.println(getCompareBaselineObjWithPredecessor
 ("http://localhost:9101/ccUCMQueryService",
 "BaseLine_Name","PVOB_Name"));
 }
}

运行该类的 main 方法 , 即可通过 SOAP Web Service 方式获得 PVOB "PVOB_Name" 中 baseline "BaseLine_Name" 与上一基线比较的版本变化 BaselineCompareInfoObj 数据对象。





回页首


结束语

本文介绍了如何使用 Java 和 SOAP Web Service 技术来开发一个 Clearcase UCM 项目信息查询程序 , 通过使用该查询程序 , 可以为以 Clearcase 作为版本控制系统的软件项目开发团队提供非常灵活的项目开发版本查询支持。这一程序还可以作为其他 Clearcase UCM 查询系统的后端数据源来使用。本文所介绍的方法也可以为 Clearcase 系统的应用方式提供一个新的思路。



参考资料

学习

获得产品和技术

讨论


关于作者

王颖初,就职于 IBM 中国软件开发中心(CSDL),他是 IBM 认证的 UNIX 平台 Rational ClearCase 管理员和 SUN 认证的 SCJP Java 程序员。SCWCD Web 组件开发员以及 SCDJWS Web 服务开发员,目前在 CSDL ECM ECMWidgets Team 从事使用 Java 技术的 Web 软件开发工作。




对本文的评价










回页首


IBM 公司保留在 developerWorks 网站上发表的内容的著作权。未经IBM公司或原始作者的书面明确许可,请勿转载。如果您希望转载,请通过 提交转载请求表单 联系我们的编辑团队。
    关于 IBM 隐私条约 联系 IBM 使用条款