RPC Software公司为家具行业开发了ERP订单管理软件。在RPC的产品出现前,从该行业的公司往往使用一些私权软件(proprietary software),这些软件基于微软Visual Studio 程序语言(如Visual Basic)、DOS解决方案和CA的Visual Object。如今的公司都在寻找能够处理很多不同业务的解决方案,例如销售(sale)、报价(quote)、订单(order entry)、时间追踪(time tracking)、仓储(warehousing)、财务管理(accounting)和报表(reporting)。 因此,能够满足这些需求的软件不仅能够不断升级,而且根本上也应该模块化。
和其他很多行业一样,近年来有一个趋势(drive)——使信息更加透明并且更接近销售者和定期与家具经销商打交道的客户。这个改变由两个方面进行驱动。一方面,从事于该产业的公司纷纷转向开放数据交换格式,例如OFDA-XML。另一方面,业务流程(如项目跟踪)使用Web应用呈报报表,这使得合作公司间共享信息成为可能。
RPC Software公司的客户要求软件能够快速而明确地适应其业务需求。他们不但要求软件拥有强大的客户端功能以便员工日常使用,而且要求软件具有为其他不同的层次业务和合作者的呈报功能。考虑到客户的这些需求,RPC Software公司决定利用开源软件作为解决方案的基础。RPC Software公司的产品线有一个基于Eclipse RCP和Apache tomat技术的ERP富客户端/服务端组件,有一套基于Web的以开源DotProject PHP应用为基础的项目管理解决方案,还有一套即将发布的基于Web的以开源SugarCRM为基础的CRM产品。
对于项目管理和CRM产品,之所以选择基于web的解决方案,是因为不必安装胖客户端,就可以在经销商、客户和销售者间共享信息。对于ERP产品,之所以选择Eclipse RCP是因为SWT/JFace部件集提供了丰富的功能并且有OSGI提供了模块化基础。
Eclipse RCP是基于OSGi规范构造的,这是其核心所在。维基百科给出的OSGi框架定义如下:
该框架能实现一个完整的、动态的组件模型。而单独的Java VM环境正好缺少这个模式。应用程序(称为bundle)无需重新引导可以被远程安装、启动、升级和卸载(其中Java包/类的管理被详细定义)API中还定义了运行远程下载管理政策的生命周期管理。服务注册允许bundles去检测新服务和取消的服务,然后相应配合。( http://zh.wikipedia.org/wiki/OSGi)(译者注:节选自http://zh.wikipedia.org/wiki/OSGi)
该CORE产品由客户端和服务器端组成。从功能组件中的代码组织到客户层和服务器层代码重用,都广泛地使用了OSGi。Eclipse RCP允许将Java类和资源文件模块化于jar文件中,这些jar文件为被称作插件(plugin)。插件是OSGI包(OSGi bundle)的扩展集。RCP Software客户端按功能分为不同的核心业务插件。RCP Software客户端同样使用了包含第三方API的插件,例如Hibernate和Jasper Reports。CORE Business服务器端也是由一组插件构成。这就使在客户端和服务器端很容易地重用业务逻辑插件成为可能。
为了进一步简化应用的部署,RPC应用已经将客户端和服务器端打包在同一个安装包中。在一个插件中,Eclipse RCP通过结合XML和配置文件,定义了入口点(entry point)概念。在众多插件中,框架利用依赖元数据以确定哪些插件需要从指定切入点加以启动。对于客户端,基于常规的exe文件的Eclipse RCP应用通过客户端切入点来完成启动过程。客户端启动时就会排除那些服务器端功能插件。相似地,当Eclipse RCP作为服务器端而运行,JNIWrapper会建立Windows服务,此时它利用的是另一个入口点来启动Eclipse RCP安装。安装中包含了Tomcat服务实例包,而UI逻辑插件或者客户端相关的插件(如:SWT插件)都不会被安装。
Core Business客户端处理与通常的ERP相关的作业,例如设立提案、装载票据材料和财务管理。服务器组件提供了基于web的数据报表的服务,这使公司机构成员间了解更高层次的汇总报告成为可能。当订单通过Core Business客户端提交,在Core Vision产品数据库中会自动创建一个产品id。Core Vision中的变化也将呈现在Core Business中。同样地,在Core Business中CRM的变化也会反映到CORE CRM中,反之亦然。RPC整合了两个数据库,而单独使用DotProject和SugarCRM的公司则没法进行这样的整合。
Core Business产品以Eclipse RCP富客户端框架应用为主。客户日常使用CORE Business客户端以满足ERP的要求,例如财务管理(accounting)、报表管理(management reporting)、工程造价(project pricing)、设立提案(proposal)等等。与其他技术相比,选择Eclipse RCP有很多理由。因为信息输入和客户数据量的要求,基于web的应用并不是可行的选择。除了Eclipse RCP和SWT,备选的富客户端部件框架还有C#和Swing,但是本地化的外观和感官是SWT的关键卖点。对RPC Software公司来说,封装于 Eclipse RCP内的功能(诸如窗体、菜单和首选项)也是相当诱人之处。
OSGI和Eclipse RCP提供的模块化已经被RPC Software公司广泛地应用于CORE Business客户端。客户通常需要例如定制报表和计算逻辑等功能,但并不是每一个客户端都需要像Time Entry这样的功能。基于插件的Eclipse RCP架构,RPC软件公司分发一系列的核心应用插件和为客户特别定制的插件,这使其满足以上需求成为可能。
Eclipse RCP的插件使用xml文件来告知核心应用该插件有哪些用途。定制报表就是RPC使用该功能的例子。下面的XML片段展示了在运行时,如何通过添加客户定制插件custom.plugin.*.core来添加定制定购报表。
<extension id="xsltTransforms" point="com.rpc.core.xsltTransforms">
<xsltTransform id="com.rpc.core.vendor.model.PurchaseOrder.pdf">
<run class="com.rpc.core.reporting.DefaultTransformSourceProvider">
<parameter name="location" value="reports/PurchaseOrder.xsl" />
</run>
</xsltTransform>
</extension>
该控制样式的优点在于,在应用运行时功能所需的配置和元信息都包括在定制插件中。而核心插件或者菜单系统没有必要知道新功能的存在。Eclipse RCP框架在运行时会就会发现和应用上述改变。
RPC Software公司不仅在CORE Business客户端使用了Eclipse RPC基于插件的架构,在基于Tomcat的CORE Business服务器端亦然。CORE Business服务器通过基于CORE Business Eclipse RCP客户端来显示已输入的数据报表。设计CORE Business Server过程中,对于重用相同逻辑和客户端已有的诸如Hibernate和Jasper Reports组件的需求越发明显。显而易见的需求重用的解决方案是将服务器端的Java类重新打包为jar文件,并将此jar文件包含在WAR文件中。在这个解决方案中,随着Java类结构的改变,原本复杂的构建脚本也需要不断的修正。实际采用的解决方案非常简单,就是让Tomcat变地对“插件敏感”。
Eclipse IDE帮助系统使用了Tomcat的内嵌版本, 这为RPC Software公司设计其需求功能提供了起点。基于这点,Servlet.jar文件被移到他们自己开发的插件里。这样,其他被创建的依赖servlet API的插件就可以使用它。已有的Tomcat插件在修改后使用Eclipse JDT编译器,并非标准的Java编译器。因此CORE Business只需捆绑JRE而不是JDK。最后,用来加载包含JSP页面的插件的类装载器,改进后被用来加载必要的系统插件,例如org.eclipse.core.runtime。
package org.eclipse.help.internal.appserver;
public class PluginClassLoaderWrapper extends URLClassLoader {
...
/**
* This is a workaround for the jsp compiler that needs to know the
* classpath.
*/
public URL[] getURLs() {
Set urls = getPluginClasspath(_plugin);
return (URL[]) urls.toArray(new URL[urls.size()]);
}
private Set getPluginClasspath(String pluginId) {
// Collect set of plug-ins
Set plugins = new HashSet();
addPluginWithPrereqs(pluginId, plugins);
// Collect URLs for each plug-in
Set urls = new HashSet();
for (Iterator it = plugins.iterator(); it.hasNext();) {
String id = (String) it.next();
try {
Bundle b = Platform.getBundle(id);
if (b != null) {
// declared classpath
String headers = (String) b.getHeaders().get(Constants.BUNDLE_CLASSPATH);
ManifestElement[] paths =ManifestElement.parseHeader(Constants.BUNDLE_CLASSPATH, headers);
if (paths != null) {
for (int i = 0; i < paths.length; i++) {
String path = paths[i].getValue();
addBundlePath(urls, b, path);
}
} else {
// RPC custom code:
try { String bundleJarPath = b.getLocation();
if (bundleJarPath.equals(Constants.SYSTEM_BUNDLE_LOCATION)) {
SystemBundle systemBundle = (SystemBundle) b;
bundleJarPath = ((SystemBundleData) systemBundle.getBundleData()).getBundleFile().getBaseFile().toURL().getPath();
bundleJarPath =bundleJarPath.substring(bundleJarPath.lastIndexOf("plugins/"));
} else if(bundleJarPath.startsWith("initial@reference:file:")) {
bundleJarPath =b.getLocation().replaceFirst("initial@reference:file:", "");
} else {
bundleJarPath =b.getLocation().replaceFirst("update@", "");
}
if (bundleJarPath.endsWith("/")) {
bundleJarPath = bundleJarPath.substring(0, bundleJarPath.lastIndexOf("/"));
}
if (bundleJarPath.startsWith("plugins/") && bundleJarPath.endsWith(".jar")) {
URL installURL = Platform.getInstallLocation().getURL();
bundleJarPath = installURL.getPath() + bundleJarPath;
urls.add(new URL(installURL.getProtocol(), installURL.getHost(), bundleJarPath));
}
} catch (Exception ex) {
}
// RPC custom code:
}
// dev classpath
String[] devpaths =DevClassPathHelper.getDevClassPath(pluginId);
if (devpaths != null) {
for (int i = 0; i < devpaths.length; i++) {
addBundlePath(urls, b, devpaths[i]);
}
}
}
} catch (BundleException e) {
}
}
return urls;
}
// RPC custom code:
private void addBundlePath(Set urls, Bundle b, String path) {
URL url = b.getEntry(path);
if (url != null) {
try {
urls.add(FileLocator.toFileURL(url));
} catch (IOException ioe) {
}
}
}
// RPC custom code:
...
}
该解决方案较之传统的Java WAR安装部署有很多优点。首先,RPC Software公司现在能够在客户端和服务器端使用相同的插件来提升重用和减少维护。其次,安装部署本质上也不复杂。对于WAR文件,RPC必须在每个客户站点上安装Tomcat,并且为WAR部署。通过将服务器捆绑成一组即用的Eclipse RCP插件,在获得可靠性的同时,能在每个客户端安装时节省大量的配置和测试。
CORE Business应用中报表兼容的发展过程也非常有意思。随着应用增多,为满足客户需求很多报表工具都被选择使用,例如Apache FO、Jasper Reports和Standard Java Printing API。一些报表是基于服务器端,而另一些报表在客户端直接运行。客户希望在不切换浏览器的前提下,能够浏览基于服务器端的报表。RPC能够通过使用嵌入浏览器组件的SWT来满足该需求。只需一些类似于下面的代码,就能在基于Eclipse RPC的客户端上直接获得基于HTML的报表,而这些报表都来自于服务器端。
final Browser browser = new Browser(shell, SWT.NONE);
browser.setUrl("http://eclipse.org");
除了显示从服务器端获得的报表,客户也能轻松地使用例如下拉组合框来在客户端直接选择报表的视图类型。随后,一个动态的URL生成,并且发送请求至服务器端。
总而言之,RPC Software公司已经发现Eclipse RCP不但可以满足其开发需求,而且是个非常健壮的框架。它可以在维护单一代码的同时,满足客户的不同需求。开源代码从根本上已经能够让其按需求增加所缺少的功能。基本上,JAVA能够通过使用众多API诸如Hibernate、Apache FO和Jasper Reports进行快速地开发。
RPC Software公司非常满意Eclipse 作为Eclipse RCP开发的一个IDE。总之,PDE开发环境能够让RCP开发像JAVA开发一样优秀。众多可做为插件使用的定制编辑器,使得手动编辑配置文件更加方便。他们已经能够利用Eclipse IDE插件(例如Jasper Report Generator插件和Eclipse TPTP 插件)的优势来加快开发。
在未来,RPC Software公司计划继续利用开源软件来增强其产品。如前所述,未来的CORE CRM产品通过利用SugarCRM提供基于web的一系列解决方案。他们同样也尝试着将用于CORE Business的众多报表技术移植到Eclipse BIRT产品中。最终,通过Eclipse Update站点,将产品更新和厂商目录分发到CORE Business客户端加以安装,这些将会下个版本的产品中实现。
查看英文原文: Eclipse RCP & OSGi on the Client & Server