[转!]嵌入Hamlet为运行OSGi的嵌入式设备编写基于Web的用户界面
来源:互联网 发布:软件项目研发计划 编辑:程序博客网 时间:2024/06/05 09:42
http://www.mcuol.com/Tech/175/15731.htm
开源的 Hamlet 框架可以用于协助 Web 开发和正确地分离内容与表示。OSGi 框架是在嵌入式设备上进行开发的一款优秀工具。这两个框架结合起来可以为最普通的小器具(比如咖啡机)提供基于浏览器的交互性。请继续跟随本文看看这是如何实现的。
Hamlet 框架用于开发基于 Web 的应用程序,它易于使用和掌握。得益于其轻量化的设计和较少的资源需求,它们也非常适合于作为嵌入式组件使用。在本文中,您将学习如何使用 Hamlet 为运行 OSGi 的嵌入式设备编写基于 Web 的用户界面。
何为 Hamlet?
Servlet 之所以能成为 Web 开发的理想选择,其原因有很多,例如很好的移植性、高效性、安全性、可扩展性和灵活性。不过,还有一些其他可行的选择可以与 servlet 的强大和简洁性相媲美。
尽管具有一些吸引人的特性,开箱即用的 servlet 还是缺少一个重要的属性:对内容和表示进行分离的支持。如果 servlet 专用于开发基于 Web 的应用程序,那么 HTML 和 Java? 代码将会不可避免地混于相同的源代码文件中。
为解决这个问题,我提出了一种易于使用和掌握的框架,称为 Hamlet,用于开发基于 Web 的应用程序(有关 Hamlet 的更多内容,请参阅 参考资料)。该框架是 IBM Radical Simplification 运动的产物,因而,您可以很快地掌握它。
Hamlet 是一个 Java servlet 扩展,它使用 Simple API for XML (SAX) 读取模板文件。在读取模板文件时,Hamlet 会使用一小组回调函数(由 HamletHandler 实现)在模板中的一些标有特殊标记和 ID 的地方添加动态内容(如图 1 所示)。
图 1. Hamlet 使用 SAX 从模板文件中读取内容并调用 HamletHandler 以添加动态内容
模板编译器可用于加速 Hamlet。模板编译器使用 SAX 读取模板文件并将内容转换为 Java 源代码。随后,它调用标准 JDK Java 编译器来生成 Java 字节码。在运行时,Hamlet 执行该代码并调用 HamletHandler 添加动态内容,如图 2 所示。有关模板编译运行机制的详细信息,请参阅 参考资料。
图 2. 模板编译器将模板文件的内容转换为 Java 字节码,再由 Hamlet 执行并调用 HamletHandler 添加动态内容
本文中所使用的项目名称 Hamlet 指的是可用来开发基于 servlet 的框架的一个内部项目名,该框架可以分离基于 Web 的应用程序中的内容和表示。Hamlets 框架 1.4 版(正式名称为 IBM Servlet-Based Content Creation Framework)可从 IBM alphaWorks 上获得(有关链接,请参阅 参考资料)。
何为 OSGi?
OSGi 联盟(即之前所熟知的 Open Services Gateway 项目)是成立于 1999 年的非盈利性独立组织。它为一种可远程管理的、基于 Java 技术的服务平台定义了开放规范。此规范由两部分组成:OSGi 框架和一组标准的服务定义。
OSGi 框架为 bundle 应用程序实现运行时环境,这些应用程序在单个 Java Virtual Machine (JVM) 中一同执行,如图 3 所示。Bundle 可安装、启动、停止、更新和在无需重启的情况下卸载。服务注册允许 在服务出现或消失时通知 bundle。之后,bundle 就可以做出相应的调整。此框架原本针对的是具有家庭自动化应用程序的 Internet 网关,但是它也已经成功应用于一些其他领域,比如自动化工业、家用电器及桌面应用程序空间。有关详细信息,请参阅标题为 “About the OSGi Service Platform” 的技术白皮书(请参阅 参考资料)。
针对 OSGi 框架已开发出大量的服务和库。Hamlet 是增强该框架的理想选择,原因是它们允许创建表示(HTML 代码)和逻辑(Java 代码)严格分离的基于 Web 的应用程序。
图 3. OSGi 架构
Hello bundle
清单 1 所示的是最基本的 OSGi 应用程序。它包含一个 Activator 类,该类实现BundleActivator 接口的 start() 和 stop() 方法。当应用程序安装完成并启动后,OSGi 框架将调用 Activator 的 start() 方法并打印出 "Hello World!"。应用程序停止时将调用 Activator 的 stop() 方法。
清单 1. Hello bundle 的 Activator 类
package com.ibm.zurich.HelloBundle;
import org.osgi.framework.*;
public class Activator implements BundleActivator {
public void start (BundleContext aContext) {
System.out.println ("Hello World!");
} // start
public void stop (BundleContext aContext) {
} // stop
} // Activator
bundle 是 OSGi 应用程序的交付和开发单元。bundle 是一个 jar 文件,其中包含应用程序的字节码和其资源。此外,它还包含一个 manifest 文件,用于描述这个 bundle。清单 2 就是对 Hello bundle 的描述。
清单 2. Hello bundle 的 manifest 文件
Manifest-Version: 1.0
Bundle-Name: Hello Bundle
Bundle-SymbolicName: hellobundle
Bundle-Version: 1.0.0
Bundle-Description: This bundle prints 'Hello World!'.
Bundle-Vendor: Rene Pawlitzek
Bundle-Activator: com.ibm.zurich.HelloBundle.Activator
Bundle-Category: example
Import-Package: org.osgi.framework
此 manifest 文件包含 bundle 的名称、版本、说明、供应商、类别和 activator,activator 是实现 ActivatorBundle 接口的类名。这个 manifest 文件还指定了导入包。有关 manifest 文件格式和语法的更多信息,请参阅 OSGi Service Platform 规范(请参阅 参考资料)。
Bundle 可方便地使用 Ant 脚本构建。我是使用 IDE 和清单 3 中所示的脚本来构建 hellobundle.jar 的。
清单 3. 使用 Ant 脚本构建 Hello bundle
在开发之前,可以使用 WinZip 或类似的工具来检查 hellobundle.jar。其中应该包含 MANIFEST.MF 和 Activator.class 文件。
符合 OSGi 服务平台规范的实现有多个,其中包括 Knopflerfish、Eclipse Equinox 和 Apache Felix 这三种开源实现(请参阅 参考资料)。我选择使用 Kopflerfish 1.3.5 实现本文中的示例。从 Knopflerfish OSGi 桌面加载 hellobundle.jar 后,您应该可以在控制台上看到打印输出的 "Hello World!"(位于窗口的底部,如图 4 所示)。
图 4. 运行 Hello bundle 的 Knopflerfish OSGi 桌面
至此,我们已经了解了如何创建、部署和运行 OSGi 应用程序。
Monitor bundle
现在,我将创建一个更实用的程序用于监视 OSGi 框架内的 bundle 和服务的生命周期。如果应用程序注册了侦听程序,那么在 bundle 被安装、启动、停止或卸载时,框架都会通知此应用程序。同样地,在服务被注册、注销或修改时,应用程序也会接到通知。功能良好的软件绝不会假定某个服务永远可用。相反,它会注册一个侦听程序来接收通知事件并相应进行调整。
Monitor 应用程序的 Activator 类实现 BundleActivator 和另外两个接口:BundleListener 和 ServiceListener。正如之前您在 Hello bundle 看到的,当 Monitor bundle 启动时,OSGi 框架会调用 start() 方法。这个应用程序并不会打印出 "Hello world!",而是使用 bundle 上下文通过 addBundleListener() 和 addServiceListener() 注册了一个 bundle 和一个服务侦听程序。当 Monitor bundle 停止后,stop() 方法将使用 removeBundleListener() 和 removeServiceListener() 移除这些侦听程序。
当服务被注册、注销或修改,或者当 bundle 被安装、启动、停止或卸载时,serviceChanged() 和 bundleChanged() 方法(二者都由 Activator 类实现)都会从 OSGi 框架接收到相应的通知事件。ServiceEvent 和 BundleEvent 类中包含有关更改的详细信息,其中包括对服务或 bundle 的引用。serviceChanged() 和 bundleChanged() 用于打印这些信息。所有这些均可在清单 4 中看到。
清单 4. Monitor bundle 的 Activator 类
package com.ibm.zurich.MonitorBundle;
import org.osgi.framework.*;
public class Activator implements BundleActivator, ServiceListener, BundleListener {
public void start (BundleContext aContext) throws Exception {
aContext.addBundleListener (this);
aContext.addServiceListener (this);
} // start
public void stop (BundleContext aContext) {
aContext.removeServiceListener (this);
aContext.removeBundleListener (this);
} // stop
/* ----- implementation of ServiceListener ----- */
public void serviceChanged (ServiceEvent aEvent) {
String action = null;
switch (aEvent.getType ()) {
case ServiceEvent.MODIFIED:
action = "modified";
break;
case ServiceEvent.REGISTERED:
action = "registered";
break;
case ServiceEvent.UNREGISTERING:
action = "unregistered";
break;
} // switch
if (action != null) {
ServiceReference ref = aEvent.getServiceReference ();
String classes[] = (String[]) ref.getProperty ("objectClass");
System.out.println ("Service '" + classes[0] + "' is " + action);
} // if
} // serviceChanged
/* ----- implementation of BundleListener ----- */
public void bundleChanged (BundleEvent aEvent) {
String action = null;
switch (aEvent.getType ()) {
case BundleEvent.INSTALLED:
action = "installed";
break;
case BundleEvent.STARTED:
action = "started";
break;
case BundleEvent.STOPPED:
action = "stopped";
break;
case BundleEvent.UNINSTALLED:
action = "uninstalled";
break;
} // switch
if (action != null) {
Bundle bundle = aEvent.getBundle ();
String name = (String) bundle.getHeaders().get (Constants.BUNDLE_NAME);
System.out.println ("Bundle '" + name + "' is " + action);
} // if
} // bundleChanged
} // Activator
Monitor bundle 的 manifest 文件和 Ant 构建脚本与 Hello bundle 几乎无异,所以这里未做解释。
HelloServlet bundle
HTTP 服务是 OSGi 框架的特性之一 ,该服务允许 servlet 的执行。下面介绍的 bundle 将提供和注册一个 Hello servlet,然后就可以通过 HTTP 获得该 servlet 了。servlet 的输出(问候语 "Hello world!")将会在显示在浏览器中。我将使用 ServiceTracker 对象(基于之前介绍的 ServiceListener 接口)在 HTTP 服务发生更改时接收通知事件。
HelloServlet bundle 的 Activator 类实现 BundleActivator 接口的 start() 和 stop() 方法。在 start() 中,将创建和存储一个 HelloServlet 实例以便稍后在的实例变量(servlet)中使用。此外,还将实例化和公开一个 ServiceTracker 对象用于跟踪 HTTP 服务。ServiceTracker 构造函数要使用 bundle 上下文(context)、要跟踪的服务名(HttpService.class.getName())和当前的 Activator 实例(this)加以调用。Activator 类实现 ServiceTrackerCustomizer 接口中的三个方法(addingService()、modifiedService() 和 removedService()),服务跟踪器会在添加、修改和删除 HTTP 服务时调用这些方法,如清单 5 所示。
清单 5. HelloServlet bundle 的 Activator 类
package com.ibm.zurich.HelloServletBundle;
import org.osgi.framework.*;
import org.osgi.util.tracker.*;
import org.osgi.service.http.*;
public class Activator implements BundleActivator, ServiceTrackerCustomizer {
public static BundleContext context = null;
private HttpService httpService;
private HelloServlet servlet;
public void start (BundleContext aContext) {
context = aContext;
servlet = new HelloServlet ();
ServiceTracker tracker =
new ServiceTracker (context, HttpService.class.getName (), this);
tracker.open ();
} // start
public void stop (BundleContext aContext) {
unregisterServlet (httpService);
servlet = null;
context = null;
} // stop
private void registerServlet (HttpService aHttpService) {
try {
if (aHttpService != null)
aHttpService.registerServlet ("/hello", servlet, null, null);
System.out.println ("Registered /hello servlet");
} catch (Exception e) {
System.out.println ("Unable to register servlet");
} // try
} // registerServlet
private void unregisterServlet (HttpService aHttpService) {
try {
if (aHttpService != null)
aHttpService.unregister ("/hello");
System.out.println ("Unregistered /hello servlet");
} catch (Exception e) {
System.out.println ("Unable to unregister servlet");
} // try
} // unregisterServlet
/* ----- implementation of ServiceTrackerCustomizer ----- */
public Object addingService (ServiceReference aRef) {
httpService = (HttpService) context.getService (aRef);
registerServlet (httpService);
return httpService;
} // addingService
public void modifiedService (ServiceReference aRef, Object aObj) {
httpService = (HttpService) context.getService (aRef);
registerServlet (httpService);
} // modifiedService
public void removedService (ServiceReference aRef, Object aObj) {
} // removedService
} // Activator
跟踪器对象最终会调用 addingService() 方法来通知您 HTTP 服务可用。可以利用这个机会注册之前在 start() 中创建的 Hello servlet 。servlet 现在就可以使用并可在 URL http://localhost:8080/hello 本地访问到,如图 5 所示。
图 5. HelloServlet bundle 向世界问好
servlet 的输出由 HelloServlet 类的 doGet() 方法中的一系列 println() 语句实现,如清单 6 所示。在 Java 代码中嵌入HTML 对于小型程序来说可以接受。但对于较大型应用程序,这样会导致很严重的维护方面的问题。Hamlet 可以解决此类问题,本文下面将介绍的 Coffee Machine 就是一个例子。
清单 6. Hello servlet
package com.ibm.zurich.HelloServletBundle;
import java.io.*;
import javax.servlet.*;
import javax.servlet.http.*;
public class HelloServlet extends HttpServlet {
public void doGet (HttpServletRequest req, HttpServletResponse res) throws
ServletException {
try {
res.setContentType ("text/html");
PrintWriter out = res.getWriter ();
out.println ("");
out.println ("
out.println ("");
out.println ("Hello world!");
out.println ("");
out.println ("");
out.flush ();
} catch (Exception e) {
throw new ServletException (e);
} // try
} // doGet
} // HelloServlet
现在,假设更加复杂的实现替代了当前的 HTTP 服务。很显然,跟踪器对象会先后调用 removedService() 方法和 addingService() 方法。并不需要在 removedService() 中注销 Hello servlet,但是必须使用新改进的 HTTP 服务重注册 servlet 以便让它再次可用。同样地,只要 HTTP 服务被修改,就必须要在 modifiedService() 中重注册 Hello servlet。Hello servlet 在 stop() 方法中注销,bundle 停止时会调用该方法。
HelloServlet bundle 的 Ant 构建脚本和 manifest 文件都与上述的其他 bundle 几乎无异。注意,清单 7 中所示的 manifest 文件指定了一些额外的导入包。
清单 7. Hello servlet
Manifest-Version: 1.0
Bundle-Name: Hello Servlet Bundle
Bundle-SymbolicName: helloservletbundle
Bundle-Version: 1.0.0
Bundle-Description: This bundles provides a servlet to print 'Hello World!'.
Bundle-Vendor: Rene Pawlitzek
Bundle-Activator: com.ibm.zurich.HelloServletBundle.Activator
Bundle-Category: example
Import-Package: org.osgi.framework, org.osgi.util.tracker, org.osgi.service.http,
javax.servlet, javax.servlet.http
Hamlet bundle
接下来,我们将创建 Hamlet bundle 以使 OSGi 基础设施可以使用 Hamlet 框架(其格式为 jar 库,名为 hamlet.jar)。这样,您就可以分离 bundle 中的 HTML 和 Java 代码了。这个 bundle 不需要 activator 类。Ant 构建脚本(如清单 8 中所示),只简单地将 hamlet.jar 和 MANIFEST.MF 打包进了一个 jar 文件,名为 hamletbundle.jar。
清单 8.构建 Hamlet bundle 的 Ant 脚本
hamletbundle.jar 的 manifest 文件(如清单 9 中所示)不仅指定了导入包,而且还指定了导出包。注意 bundle 的类别现在是 lib。
清单 9. Hamlet bundle 的 Manifest
Manifest-Version: 1.0
Bundle-Name: Hamlet Bundle
Bundle-SymbolicName: hamletbundle
Bundle-Version: 1.0.0
Bundle-Description: This bundle provides the Hamlets framework.
Bundle-Vendor: Rene Pawlitzek
Bundle-ClassPath: hamlet.jar
Bundle-Category: lib
Export-Package: com.ibm.hamlet, com.ibm.hamlet.helpers
Import-Package: org.osgi.framework, javax.servlet, javax.servlet.http, org.apache.log4j,
org.xml.sax, org.xml.sax.helpers
当我们从 Knopflerfish OSGi 桌面加载 Hamlet bundle 时,它会显示在 bundle 视图中,如图 6 所示。
图 6. Hamlet bundle 显示在 Knopflerfish OSGi 桌面中
目前,Hamlet 框架依赖于 Log4j 实现日志服务。因此,我们需要构建和安装一个 Log4j bundle(包含 log4j.jar);其方法与构建 Hamlet bundle 相同。(请参阅 参考资料 获得到 Log4j 的链接。)
Coffee Machine bundle
在最后这个示例中,我将开发一个 bundle,它可以为支持网络的咖啡机提供一个基于 Web 的用户界面。您可以在世界上的任何地方使用浏览器设置咖啡机的定时器。我将使用 Hamlet 来分离表示和逻辑,所以您不会在 Java 代码中看到任何 HTML,反之亦然。此外,还将使用 model-view-controller (MVC) 设计模式用于实现。
清单 10 中所示的 Activator 类还是使用 start() 和 stop() 两个方法实现 BundleActivator 接口。OSGi 框架首先调用 start(),其中 BasicConfigurator.configure() 用于对日志记录进行配置,CoffeeMachineTimer.getTimer().start() 用于对咖啡机模型 (M) 进行初始化。接下来,创建咖啡机的视图 (V) 和控制器 (C)。视图是 CoffeeMachine 的一个实例,是一个 Hamlet,而控制器则是 CoffeeMachineController 的一个实例,是一个 servlet(如图 7 所示)。最后,与在 HelloServlet bundle 中一样,实例化并公开一个 ServiceTracker 对象用于跟踪 HTTP 服务的生命周期。只要 HTTP 服务有所更改,跟踪器就会调用 addingService()、modifiedService() 和 removedService(),这些方法是 ServiceTrackerCustomizer 接口的一部分,由 Activator 实现。
图 7. CoffeeMachine bundle 的 MVC 设计模式
清单 10. CoffeeMachine bundle 的 Activator 类
package com.ibm.zurich.CoffeeMachineBundle;
import javax.servlet.http.*;
import org.apache.log4j.*;
import org.osgi.framework.*;
import org.osgi.util.tracker.*;
import org.osgi.service.http.*;
public class Activator implements BundleActivator, ServiceTrackerCustomizer {
public static BundleContext context = null;
// log4j
private static Category category = Category.getInstance (Activator.class.getName ());
private HttpService httpService;
private HttpServlet controller;
private CoffeeMachine view;
public void start (BundleContext aContext) {
context = aContext;
BasicConfigurator.resetConfiguration ();
BasicConfigurator.configure ();
CoffeeMachineTimer.getTimer().start ();
view = new CoffeeMachine ();
controller = new CoffeeMachineController ();
ServiceTracker tracker =
new ServiceTracker (context, HttpService.class.getName (), this);
tracker.open ();
} // start
public void stop (BundleContext aContext) {
unregister (httpService);
CoffeeMachineTimer.getTimer().stop ();
controller = null;
view = null;
context = null;
} // stop
private void register (HttpService aHttpService) {
try {
if (aHttpService != null) {
aHttpService.registerServlet ("/CoffeeMachine", view, null, null);
category.debug ("Registered '/CoffeeMachine' hamlet");
aHttpService.registerServlet ("/CoffeeMachineController",
controller, null, null);
category.debug ("Registered '/CoffeeMachineController' servlet");
HttpContext httpContext = new CoffeeMachineContext (context);
aHttpService.registerResources ("/Include", "/Resources", httpContext);
category.debug ("Registered '/Include' resources");
} // if
} catch (Exception e) {
category.error ("Unable to register", e);
} // try
} // register
private void unregister (HttpService aHttpService) {
try {
if (aHttpService != null) {
aHttpService.unregister ("/CoffeeMachine");
category.debug ("Unregistered '/CoffeeMachine' hamlet");
aHttpService.unregister ("/CoffeeMachineController");
category.debug ("Unregistered '/CoffeeMachineController' servlet");
aHttpService.unregister ("/Include");
category.debug ("Unregistered '/Include' resources");
} // if
} catch (Exception e) {
category.error ("Unable to unregister", e);
} // try
} // unregister
/* ----- implementation of ServiceTrackerCustomizer ----- */
public Object addingService (ServiceReference aRef) {
category.debug ("adding service");
httpService = (HttpService) context.getService (aRef);
register (httpService);
return httpService;
} // addingService
public void modifiedService (ServiceReference aRef, Object aObj) {
category.debug ("modified service");
httpService = (HttpService) context.getService (aRef);
register (httpService);
} // modifiedService
public void removedService (ServiceReference aRef, Object aObj) {
category.debug ("removed service");
} // removedService
} // Activator
有时,跟踪器对象会调用 addingService() 通知您 HTTP 服务的可用性。随后,会调用 register() 向 HTTP 服务注册 Hamlet 提供的视图、servlet 提供的控制器以及一些资源(用来格式化输出的咖啡机位图和级联样式表)。
使用 registerResources() 注册资源需要一个 HTTP 上下文(httpContext)。CoffeeMachineContext 类(如清单 11 所示)用于为咖啡机资源提供 HTTP 上下文。它实现了 HttpContext 接口的三个方法:handleSecurity()、 getMimeType() 和 getResource()。当请求某个特定的资源时(例如,使用 请求咖啡机位图),CoffeeMachineContext 类的 getResource() 方法会返回一个指向该资源(位于 bundle 中)的 URL(例如,bundle://36/Resources/coffee_machine.jpg)。
清单 11. CoffeeMachine 上下文
package com.ibm.zurich.CoffeeMachineBundle;
import java.net.*;
import javax.servlet.http.*;
import org.osgi.framework.*;
import org.osgi.service.http.*;
public class CoffeeMachineContext implements HttpContext {
private BundleContext context;
public CoffeeMachineContext (BundleContext context) {
this.context = context;
} // CoffeeMachineContext
public boolean handleSecurity (HttpServletRequest req, HttpServletResponse res) {
return true;
} // handleSecurity
public String getMimeType (String name) {
return null;
} // getMimeType
public URL getResource (String path) {
return context.getBundle().getResource (path);
} // getResource
} // CoffeeMachineContext
可以通过在浏览器中输入 URL http://localhost:8080/CoffeeMachine 访问咖啡机的基于 Web 的用户界面,如图 8 所示。
图 8. CoffeeMachine bundle 为咖啡机提供了基于 Web 的前端
HTTP 服务被替换后,跟踪器对象将会先后调用 removedService() 方法以及 addingService() 方法。与 HelloServlet bundle 的情况一样,我们需要重新注册控制器 servlet、资源和能提供视图的 Hamlet。在调用了 modifiedService() 后,换句话说就是 HTTP 被修改后,也需要进行重新注册。
CoffeeMachine bundle 停止时,将调用 Activator 的 stop() 方法。在这里,我们使用 CoffeeMachineTimer.getTimer().stop() 来注销 Hamlet、servlet 和资源以及清除模型 (M)。
到目前为止,与前面的 HelloServlet 示例的区别并不大,但是,CoffeeMachine bundle 没有在 servlet 的 doGet() 方法中使用 println() 语句来生成输出,而是使用了 Hamlet 来分离 HTML 和 Java 代码。
通过网络访问咖啡机时,将调用 doGet() 方法。我们使用 model.getTiming() 从数据模型检索当前的定时器设置并创建了一个 CoffeeMachineHandler(它是 CoffeeMachine 的私有类,扩展了 HamletHandler)的实例(handler)。
接下来,将调用 serveDoc (req, res, template, handler) 方法。该方法执行 template 所引用的编译模板生成咖啡机的用户界面(HTML 代码),并调用 handler 的 getElementAttributes() 方法使用 Helpers.getAttributes() 添加当前的定时器设置( CHECKED 和 SELECTED 属性)。此编译模板的 Java 字节码 (CoffeeMachineTemplate.class) 在设计时由模板编译器使用 Ant 根据 XHTML 模板 (CoffeeMachineTemplate.html) 生成。然后我们在使用 init() 方法中的 Class.forName("CoffeeMachineTemplate.class") 创建 CoffeeMachine hamlet 的过程中加载此代码。有关 getElementAttributes() 如何运作的详细信息,请参阅 参考资料。
清单 12. CoffeeMachine hamlet 提供了咖啡机视图
package com.ibm.zurich.CoffeeMachineBundle;
import javax.servlet.*;
import javax.servlet.http.*;
import com.ibm.hamlet.*;
import com.ibm.hamlet.helpers.*;
import org.apache.log4j.*;
import org.xml.sax.*;
public class CoffeeMachine extends Hamlet {
// log4j
private static Category category =
Category.getInstance (CoffeeMachine.class.getName ());
private Class template;
private static class CoffeeMachineHandler extends HamletHandler {
private CoffeeMachineTiming timing;
public CoffeeMachineHandler (Hamlet hamlet, CoffeeMachineTiming aTiming) {
super (hamlet);
timing = aTiming;
} // CoffeeMachineHandler
public Attributes getElementAttributes (String id, String name, Attributes atts)
throws Exception {
if (id.equals ("Checkbox")) {
if (timing.isSet ())
atts = Helpers.getAttributes (atts, "CHECKED", "Checked");
} else if (id.equals ("StartHour")) {
String hour = atts.getValue ("Hour");
if (hour.equals ("" + timing.getHour ()))
atts = Helpers.getAttributes (atts, "SELECTED", "Selected");
} else if (id.equals ("StartMinute")) {
String minute = atts.getValue ("Minute");
if (minute.equals ("" + timing.getMinute ()))
atts = Helpers.getAttributes (atts, "SELECTED", "Selected");
} // if
return atts;
} // getElementAttributes
} // CoffeeMachineHandler
public void init () throws ServletException {
try {
category.debug ("init");
template = Class.forName ("CoffeeMachineTemplate");
} catch (Exception e) {
category.error ("", e);
throw new ServletException (e);
} // try
} // init
public void doGet (HttpServletRequest req, HttpServletResponse res)
throws ServletException {
try {
category.debug ("doGet");
CoffeeMachineTimer model = CoffeeMachineTimer.getTimer ();
CoffeeMachineTiming timing = model.getTiming();
HamletHandler handler = new CoffeeMachineHandler (this, timing);
serveDoc (req, res, template, handler);
} catch (Exception e) {
category.error ("", e);
throw new ServletException (e);
} // try
} // doGet
public void destroy () {
category.debug ("destroy");
} // destroy
} // CoffeeMachine
清单 13 显示了咖啡机的 HTML 代码 (CoffeeMachineTemplate.html),模板编译器会在设计时将该代码转换成 Java 字节码 (CoffeeMachineTemplate.class)。有关模板编译运作原理的详细信息,请参阅 参考资料。
清单 13. CoffeeMachineTemplate.html
]>
现在,假设用户想要设置咖啡机的定时器。正如您在清单 14 中看到的,用户界面不需要提供提交按钮,其原因是:有了 标签和两个
清单 14. CoffeeMachineController servlet 提供咖啡机控制器 (C)
package com.ibm.zurich.CoffeeMachineBundle;
import javax.servlet.*;
import javax.servlet.http.*;
import org.apache.log4j.*;
public class CoffeeMachineController extends HttpServlet {
// log4j
private static Category category =
Category.getInstance (CoffeeMachineController.class.getName ());
public void init () {
category.debug ("init");
} // init
public void doPost (HttpServletRequest req, HttpServletResponse res)
throws ServletException {
try {
category.debug ("doPost");
String set = req.getParameter ("Checkbox");
String hour = req.getParameter ("Hour");
String minute = req.getParameter ("Minute");
CoffeeMachineTimer model = CoffeeMachineTimer.getTimer ();
CoffeeMachineTiming timing = new CoffeeMachineTiming ();
timing.set ("on".equals (set));
timing.setHour (hour);
timing.setMinute (minute);
model.setTiming (timing);
res.sendRedirect ("CoffeeMachine");
} catch (Exception e) {
category.error ("", e);
throw new ServletException (e);
} // try
} // doPost
public void destroy () {
category.debug ("destroy");
} // destroy
} // CoffeeMachineController
定时器设置发生更改时,将调用 CoffeeMachineController 的 doPost() 方法。使用 getParameter() 检索提交的参数值,然后创建一个 CoffeeMachineTiming 的实例 (timing) 并使用其 setter 方法设置小时、分钟和定时器状态(开或关)。接下来,调用咖啡机模型 (M) 中的 setTiming() 方法(使用 CoffeeMachineTimer.getTimer() 获得)来设置新的定时器设置。最后,重定向到 CoffeeMachine hamlet 并刷新视图。这样一来,处理输入的逻辑(servlet)和生成视图的逻辑(Hamlet)就巧妙地分离开来了(如 MVC 设计模式所描述的那样)。
CoffeeMachineTiming 类直观明了,如清单 15 所示。它表示咖啡机定时器的设置。
清单 15. CoffeeMachineTiming 类表示定时器的设置
package com.ibm.zurich.CoffeeMachineBundle;
public class CoffeeMachineTiming {
private boolean set;
private int hour;
private int minute;
public CoffeeMachineTiming () {
set = false;
hour = 12;
minute = 0;
} // CoffeeMachineTiming
public CoffeeMachineTiming (CoffeeMachineTiming aTimingDesc) {
set = aTimingDesc.set;
hour = aTimingDesc.hour;
minute = aTimingDesc.minute;
} // CoffeeMachineTiming
public void set (boolean b) {
set = b;
} // set
public boolean isSet () {
return set;
} // isSet
public void setHour (String aHour) {
hour = Integer.parseInt (aHour);
} // setHour
public int getHour () {
return hour;
} // getHour
public void setMinute (String aMinute) {
minute = Integer.parseInt (aMinute);
} // setMinute
public int getMinute () {
return minute;
} // getMinute
public String toString () {
StringBuffer buf = new StringBuffer ();
buf.append ("Set: ");
buf.append (set);
buf.append (", ");
buf.append ("Hour: ");
buf.append (hour);
buf.append (", ");
buf.append ("Minute: ");
buf.append (minute);
return buf.toString ();
} // toString
public static void main (String args[]) {
CoffeeMachineTiming timing = new CoffeeMachineTiming ();
System.out.println (timing.toString ());
} // main
} // CoffeeMachineTiming
CoffeeMachineTimer 类(如清单 16 所示)提供了一种触发煮咖啡过程的机制。它实现了 Runnable 接口的 run() 方法并使用线程来检查是否该开始煮咖啡了。目前,定时器开始计数后,程序只简单地打印消息 "Brewing coffee..." ,但是您可以很容易地使用与真实咖啡机交互的功能替代 println() 语句(我很希望有人能够如此尝试!)。由于我们所使用的是单件模式 (singleton pattern),因此使用静态方法 getTimer() 仅会创建一个 CoffeeMachineTimer 实例()。start() 和 stop() 方法用于启动和停止定时器,getTiming() 和 setTiming() 则允许您获得当前的定时器设置并设置新的定时器设置。注意,为了避免出现竞争现象,需要进行同步。
清单 16. CoffeeMachineTimer 类提供了咖啡机模型(M)
package com.ibm.zurich.CoffeeMachineBundle;
import java.util.*;
import org.apache.log4j.*;
class CoffeeMachineTimer implements Runnable {
// log4j
private static Category category =
Category.getInstance (CoffeeMachineTimer.class.getName ());
private static CoffeeMachineTimer timer = null;
private boolean abort;
private Thread t;
private CoffeeMachineTiming timing;
CoffeeMachineTimer () {
t = null;
abort = false;
timing = new CoffeeMachineTiming ();
} // CoffeeMachineTimer
public synchronized void start () {
if (t == null) {
category.debug ("Starting timer...");
t = new Thread (this);
t.start ();
} // if
} // start
public synchronized void stop () {
if (t != null) {
category.debug ("Stopping timer...");
abort = true;
try {
t.join ();
} catch (Exception e) {
category.error ("", e);
} // try
} // if
} // stop
public synchronized CoffeeMachineTiming getTiming () {
return new CoffeeMachineTiming (timing);
} // getTiming
public synchronized void setTiming (CoffeeMachineTiming aTiming) {
timing = new CoffeeMachineTiming (aTiming);
System.out.println (timing.toString ());
} // setTiming
public void run () {
category.debug ("Running...");
while (!abort) {
try {
CoffeeMachineTiming timing = getTiming ();
if (timing.isSet ()) {
long curTime = System.currentTimeMillis ();
Calendar cal = new GregorianCalendar ();
cal.setTimeInMillis (curTime);
cal.set (Calendar.HOUR_OF_DAY, timing.getHour ());
cal.set (Calendar.MINUTE, timing.getMinute ());
cal.set (Calendar.SECOND, 0);
long setTime = cal.getTimeInMillis ();
Date d1 = new Date (curTime);
Date d2 = new Date (setTime);
System.out.println (d1.toString () + ", " + d2.toString ());
if ((curTime / 1000L) == (setTime / 1000L))
System.out.println ("Brewing coffee ... ");
} // if
Thread.sleep (1000);
} catch (Exception e) {
category.error ("", e);
} // try
} // while
category.debug ("Finished.");
} // run
public static synchronized CoffeeMachineTimer getTimer () {
if (timer == null)
timer = new CoffeeMachineTimer ();
return timer;
} // getTimer
} // CoffeeMachineTimer
这个 bundle 的 manifest 文件如清单 17 所示,它指定了所需的导入包。
清单 17. CoffeeMachine bundle 的 manifest 文件
Manifest-Version: 1.0
Bundle-Name: Coffee Machine Bundle
Bundle-SymbolicName: coffeemachinebundle
Bundle-Version: 1.0.0
Bundle-Description: This bundles provides a network-enabled coffee machine.
Bundle-Vendor: Rene Pawlitzek
Bundle-Activator: com.ibm.zurich.CoffeeMachineBundle.Activator
Bundle-Category: example
Import-Package: org.osgi.framework, org.osgi.util.tracker, org.osgi.service.http,
javax.servlet, javax.servlet.http, com.ibm.hamlet,
com.ibm.hamlet.helpers, org.apache.log4j, org.xml.sax, org.xml.sax.helpers
最后,清单 18 中给出了用于构建 CoffeeMachine bundle 的 Ant 脚本。它包含一个 template 任务用于调用模板编译器将所有的 *Template.html 文件(在本例中即为 CoffeeMachineTemplate.html)转换为 Java 类 (CoffeeMachineTemplate.class)。Hamlet 通过执行这些 Java 类生成用户界面。请注意,*Template.html 文件无需包含在 bundle 中。只有在模板编译时才需要它们。应用程序中的各种资源(所有 gif、jpg 和 css 文件)都是 bundle 的一部分,它们位于 Resources 子目录中。
清单 18. 构建 CoffeeMachine bundle 所需的 Ant 脚本
至此,这个支持网络的咖啡机示例就完成了。
结束语
OSGi 框架是一种基于 Java 技术的服务平台,具有远端管理功能。它已经成功应用于许多领域:家庭自动化、自动化工业、家用电器,甚至是桌面应用程序。Hamlet是 OSGi 框架的有益补充,因为使用它们可以创建出表示层和逻辑层(提供动态内容)完全分离的基于 Web 的应用程序。本文首先对 Hamlet 作了简要介绍,然后逐步展示了如何使用 Hamlet 创建支持网络的咖啡机,可以使用浏览器远程设置该咖啡机的定时器。
- [转!]嵌入Hamlet为运行OSGi的嵌入式设备编写基于Web的用户界面
- OSGI 中嵌入 Http 服务器的运行环境
- OSGi 框架嵌入 Http 服务器的运行环境配置
- OSGI 中嵌入 Http 服务器的运行环境
- 打造一个基于OSGi的Web Application
- 打造一个基于OSGi的Web Application
- 打造一个基于OSGi的Web Application
- 打造一个基于OSGi的Web Application
- 基于OSGi bundle的Web工程
- 搭建一个基于OSGI的可以运行helloworld.html和helloworld.jsp的简单web环境
- 适用于多种OSGi框架的WebConsole与OSGi嵌入到Web应用的实现
- “应该为在运行时可能编辑的任何文本嵌入字体,具有”使用设备字体”设置的文本除外。使用”文本” > “字体嵌入”命令嵌入字体。”
- 基于Web的嵌入式设备管理
- 用户界面的编写
- 部署并运行你的OSGi Web应用程序
- 部署并运行你的OSGi Web应用程序
- 应该为在运行时可能编辑的任何文本嵌入字体,具有"使用设备字体"设置的文本除外。
- 基于OSGi的Web应用开发系列一(转帖)
- 黑客教程系列-简明批处理第三章
- 春运火车站的十种死法
- 期权
- 『转!』如何在嵌入式系统上运行OSGI架构
- 数据库用户修改密码后,SDE服务无法启动的处理过程
- [转!]嵌入Hamlet为运行OSGi的嵌入式设备编写基于Web的用户界面
- 黑客教程系列-简明批处理第四章
- [转!]OSGi和嵌入式Jetty
- JavaScript对象及继承教程 1
- JAVA TEST 2
- JavaScript对象与继承教程之内置对象(下)
- 罗列整一年的日期
- [转载]回环接口(loop-back/loopback)
- 黑客教程系列-简明批处理第五章