alartin's profileWindows Live 共享空间PhotosBlogListsMore Tools Help

Blog


    January 22

    Facelet: JSF的最佳拍档

    阅读这篇文章之前,我们先回顾一下,为什么我们需要JSF。很显然,我们不满意基于页面这种杂乱的Web视图设计体系。JSF就是提供一种基于组件,具有事件模型和组件模型的方式让我们象开发Swing图形界面一样开发Web程序。很美妙?不是么?

    那么我们为什么还需要Facelet呢?我想这一定是大家最关心的问题。

    文章列表:

    1. Inside Facelet: part one
    2. Inside Facelet: part two
    3. Facelet: best with JSF
    4. Improving JSF by Dumping JSP

    JBPM术语:ProcessInstance和ProcessDefinition

    概述

    ProcessInstance是ProcessDefinition的一个执行实例,想象一下对于订票流程,每个客户的订票动作都会根据订票流程定义而创建一个流程实例,也就是执行实例ProcessInstance.创建执行实例很简单:ProcessInstance(ProcessDefinition def)。当一个ProcessInstance被创建后,负责执行主路径的token也被创建,这个token就是根token(root token),根token此时位于流程定义的开始状态start state.

    JBPM术语:事件Event

    概述

    Event反映的是流程执行中的各个时刻。在流程执行中JBPM引擎会在计算下一个状态的时候触发各种事件。一个事件通常和流程定义中的一个元素相关联,比如流程定义本身,节点或者转移。大部分的元素能够触发不同类型的事件,比如一个节点可以触发节点进入事件,节点离开事件。事件其实是和动作连接在一起的。每个事件维护一个动作列表。当JBPM引擎触发一个事件的时候,该事件维护的动作列表中的动作将被执行。

    事件类型

    在JBPM中事件类型是写死在事件类中的,共有16种:

    1. EVENTTYPE_TRANSITION = "transition"; // 转移
    2. EVENTTYPE_BEFORE_SIGNAL = "before-signal"; // 发信号前
    3. EVENTTYPE_AFTER_SIGNAL = "after-signal"; // 发信号后
    4. EVENTTYPE_PROCESS_START = "process-start"; // 处理开始状态
    5. EVENTTYPE_PROCESS_END = "process-end"; // 处理结束状态
    6. EVENTTYPE_NODE_ENTER = "node-enter"; // 进入节点
    7. EVENTTYPE_NODE_LEAVE = "node-leave"; // 离开节点
    8. EVENTTYPE_SUPERSTATE_ENTER = "superstate-enter"; // 进入超级状态
    9. EVENTTYPE_SUPERSTATE_LEAVE = "superstate-leave"; // 离开超级状态
    10. EVENTTYPE_SUBPROCESS_CREATED = "subprocess-created"; // 子流程创建
    11. EVENTTYPE_SUBPROCESS_END = "subprocess-end"; // 子流程结束
    12. EVENTTYPE_TASK_CREATE = "task-create"; // 任务创建
    13. EVENTTYPE_TASK_ASSIGN = "task-assign"; // 任务分派
    14. EVENTTYPE_TASK_START = "task-start"; // 任务启动
    15. EVENTTYPE_TASK_END = "task-end"; // 任务结束
    16. EVENTTYPE_TIMER = "timer"; // 定时器

    JBPM术语:超级状态Superstates

    概述

    一个超级状态就是一组节点。超级状态可以以回归方式嵌套。超级状态能够给流程定义带来层次效果。例如一个应用可能要把流程中的所有节点按阶段进行分组。动作可以和超级状态的事件相关联。超级状态能够让一个token在给定的时间上位于多个嵌套的节点中。这会带来很多方便,例如你可以方便的查询流程的执行是否在启动阶段。在JBPM中你可以将任意的节点们组成一个超级状态。

    超级状态的转移

    超级状态的所有转移都通过超级状态中的节点们的token来完成。转移也可以到达超级状态。此时,token被重定向到超级状态中的第一个节点。超级状态外的节点可以拥有指向超级状态内节点的转移,当然同时超级状态内的节点也可以拥有指向超级状态外的节点的转移,甚至指向超级状态本身的转移。超级状态甚至可以有自引用。

    超级状态的事件

    超级状态拥有两个特定的事件:进入超级状态,离开超级状态。无论超级状态

    JBPM: 上下文

    概述

    上下文Context其实就是处理流程变量的地方。流程变量使用键值对来维护流程实例相关的信息。上下文必须要存储到数据库中。

    访问变量

    我们通过上下文实例(ContextInstance)来和流程变量打交道,上下文实例就是一个中央接口,位于org.jbpm.context.exe包中,从流程实例ProcessInstance中获得上下文实例:

    ProcessInstance processInstance = ...;
    ContextInstance contextInstance = (ContextInstance) processInstance.getInstance(ContextInstance.class);

    常见的对上下文实例的操作包括:设置变量,获得变量:

    void ContextInstance.setVariable(String variableName, Object value);
    void ContextInstance.setVariable(String variableName, Object value, Token token);
    Object ContextInstance.getVariable(String variableName);
    Object ContextInstance.getVariable(String variableName, Token token);
    变量的名字是String类型的。默认情况下,JBPM支持多种变量值的类型:
    • java.lang.String
    • java.lang.Boolean
    • java.lang.Character
    • java.lang.Float
    • java.lang.Double
    • java.lang.Long
    • java.lang.Byte
    • java.lang.Short
    • java.lang.Integer
    • java.util.Date
    • byte[]
    • java.io.Serializable
    • 使用hibernate持久化的类

    Null值也支持,也可持久化。其他类型的也可以存储在流程变量中,只是会抛出异常。当流程变量的值为hibernate存储的类的时候需要配置JBPM.

    流程变量的生命周期

    流程变量无需在流程文件中声明,在运行时,你可以将任意Java对象设为变量。如果该变量不存在的话,变量会被创建,就象Map一样。

    删除变量也很简单:

    ContextInstance.deleteVariable(String variableName);
    ContextInstance.deleteVariable(String variableName, Token token);
    JBPM现在支持自动改变类型。这意味着你可以使用不同类型的值来覆盖一个变量。当然你应该严格控制类型改变的次数,毕竟这需要额外的数据库通讯。
    流程变量的持久化
    流程变量是流程实例的一部分。将流程实例保存在数据库中,意味着数据库和流程实例同步。流程变量的创建更新删除是通过流程实例的更新来实现的。
    流程变量的范围
    每个token,也就是每个流程执行的路径都有一套自己的变量。请求变量总是在token上发生的。流程实例拥有一个token树。在请求变量的时候,当你没有指定token参数的话,默认的是root token作为参数。
    变量的查询是在给定的token的父token们中递归查找的。类似编程语言中的变量范围。
    如果你在一个token上设置一个并不存在的变量,那么这个变量将在root token上创建。这意味这每个变量的默认范围是流程范围。
    如果你需要限定变量的范围,则需要显式地指定Token:
    ContextInstance.createVariable(String name, Object value, Token token);
    变量的过载overloading
    变量的过载指的是流程执行的每个路径都可以有相同一套名字的变量,也就是说不同的token中变量的名字可以一样,值和类型可以不一样。token之间是独立的。这在流程跨越同一个转移,启动不同的并发路径时很有用。
    区别不同执行路径的就靠它们各自的这些变量。
    变量的重载overriding
     
     

    JBPM: 任务的管理 Task Management

    概述

    JBPM的核心就是持久化流程。持久化流程中最重要的特性就是管理人们的任务和任务列表。JBPM允许我们设定程序为人工参与进入等待状态。

    任务

    任务是流程定义的一部分。任务负责定义流程执行中任务实例task instance如何必须被创建和指派。所以任务也就是任务定义。任务可以在任务节点中定义,也可以在流程定义中定义。不过通常的方法是在任务节点中定义一个或者多个任务。这意味着任务节点task node代表着一个用户的人工任务,流程执行必须等待直到参与者actor(也就是用户)完成这项人工任务。当参与者actor完成人工任务后,流程执行将继续。如果一个任务节点中指定了多个任务,默认的行为是必须流程执行必须等待所有任务都完成

    流程定义中也能定义任务。在流程定义中的任务能够通过名字查找,在任务节点中被引用,或者内部动作inside actions中使用。事实上,无论任务在流程定义中定义还是在任务节点中定义,只要任务有名字,都能够在流程定义中查找。需要注意的是:任务的名字在整个流程定义中必须唯一的。

    任务可以有优先级。优先级在这个任务的每个任务实例中作为初始优先级。任务实例task instance能够在日后修改这个优先级。

    任务实例 task instance

    一个任务实例能够被指派给一个参与者ID, actorId(一个字符串)。所有的任务实例都在JBPM的JBPM_TASKINSTANCE表中存储。我们可以通过这张表查询给定actorId的所有任务实例,通过这种方法你可以获得特定用户的任务列表。JBPM任务列表机制能够将JBPM任务和其他任务结合起来,甚至能够结合那些和流程执行不相关的任务。通过这种方式,JBPM的开发者能够容易的将JBPM流程任务和其他系统的任务在一个同一的任务列表仓库同一管理。

    任务实例的生命周期

    任务实例的生命周期非常直观:创建后,任务实例就可以开始了,然后,可以被结束,这意味着任务实例被标记为完成。注意:为了灵活性,任务的指派并不是任务实例生命周期的一部分。所以任务实例可以被指派了,也可以尚未指派,这丝毫不影响任务实例的生命周期。任务实例通常是在流程执行进入任务节点的时候被创建。创建的方法是TaskMgmtInstance.createTaskInstance()。然后,用户界面组件可以通过TaskMgmtInstance.findTaskInstanceByActorId()方法查询任务列表。用户界面组件可以调用TaskInstance.assign(String), TaskInstance.start(), TaskInstance.end()等方法。

    任务实例通过日期属性来维护它的状态:create, start, end. 可以通过TaskInstance相应getter方法访问。目前完成了的任务实例将会通过结束日期被标记为完成,因此对于后续查询任务列表的时候,完成了的任务实例不会被包含在任务列表中,不过它们仍旧在JBPM_TASKINSTANCE表中

    任务实例和图的执行

    从某种意义上说,任务实例是参与者actor任务列表上的项目。任务实例可以信号化,信号化的任务实例可以在任务结束的时候向token发出signal,让流程执行继续下去。任务实例也可以拦截化/阻止化,这意味着相关的token在任务未完成前不能离开任务节点。任务实例默认是信号化的

    在一个任务节点中有多个任务实例的情况下,流程的开发者可以指定任务实例的完成将如何影响流程执行的继续。通过任务节点的signal属性的各种值来控制这一点:

    • last: 这是默认的。当最后一个任务完成时,流程执行将继续。当节点的入口处没有任务被创建的时候,流程执行将继续。
    • last-wait:当最后一个任务完成时,流程执行将继续。当节点的入口处没有任务被创建的时候,流程执行将在任务节点中等待直到任务被创建为止。
    • first:当第一个任务完成时,流程执行将继续。当节点的入口处没有任务被创建的时候,流程执行将继续。
    • first-wait:当第一个任务完成时,流程执行将继续。当节点的入口处没有任务被创建的时候,流程执行将在任务节点中等待直到任务被创建为止。
    • unsynchronized: 流程执行将永远继续下去,无论任务是否被创建或者仍未完成。
    • never: 流程执行将不再继续,无论任务是否被创建或者仍未完成,和unsynchronized相对。

    任务实例也可以通过运行时计算而创建。在这种情况下,在任务节点的进入节点事件中添加一个ActionHandler,并且将属性create-task设为false(意味着进入任务节点并不创建任务,而是让动作监听进入节点事件,在动作中运行时计算), 下面是个例子:

    public class CreateTasks implements ActionHandler {
      public void execute(ExecutionContext executionContext) throws Exception {
        Token token = executionContext.getToken();
        TaskMgmtInstance tmi = executionContext.getTaskMgmtInstance();
          
        TaskNode taskNode = (TaskNode) executionContext.getNode();
        Task changeNappy = taskNode.getTask("change nappy");
    
        // now, 2 task instances are created for the same task.
        tmi.createTaskInstance(changeNappy, token);
        tmi.createTaskInstance(changeNappy, token);
      }
    }
    我们提到过,任务可以在任务节点中定义,也可以在流程定义中定义。如果在流程定义中定义,我们可以通过TaskMgmtDefinition获取。
    TaskMgmtDefinition继承了ProcessDefinition, 提供了任务管理信息
    完成一个任务实例很简单,调用TaskInstance.end()方法就可以了。你也可以在这个方法中指定一个转移transition,这样的话,任务实例完成
    后将触发流程执行继续下去,离开任务节点,走向指定的转移。
    任务指派
    流程定义中包含了任务节点。一个任务节点可以包含零个或多个任务。什么是任务呢?任务其实就是流程定义中静态描述的一部分(是说任务仅仅是个描述而已)。在运行时,任务实例
    将根据任务这个描述创建。而一个任务实例可以说就是一个人的任务列表中的一项。任务实例总是要被指派的。
    JBPM的任务指派模型分为PULL和PUSH.
    任务指派的数据模型
    image 
    任务实例或泳道实例的参与者ID actorId负责这个任务。而其中的PooledActors, 参与者候选池维护一系列任务的候选者,每个候选者都有可能负责这个任务,当候选者获得这个任务
    后,他就为这个任务负责。需要注意的是:参与者ID和参与者候选池是可选的,也可以组合在一起使用。
    个人的任务列表
    个人任务列表负责维护指派给特定个人的任务,通过任务实例的参与者ID就可以看出。所以要想将任务实例放入个人任务列表中,只需下列方法的一种:
    • 在流程定义中的task元素的actor-id属性中指定表达式
    • 也可以在代码的任何地方设置TaskInstance.setActorId(String)方法
    • 也可以在一个AssignmentHandler中调用Assignable.setActorId(String)方法

    我们可以通过TaskMgmtSession.findTaskInstances(String actorId)来获取特定人的任务列表。

    组的任务列表

    参与者候选池指的是任务实例的候选者。这意味着这个任务可以提供给多个用户,其中一个候选者可以被设置接受这个任务。候选池中的参与者可以看作是同一个组的,如果这个任务是提供给候选池的,用户必须先将任务拿到个人的任务列表中,才能执行任务。

    TODO

    背后的动机是我们希望将Identity组件从JBPM的任务指派中剥离。JBPM应该只关注actorId这些String类型的对象,而不应该关注用户,组或者其他识别信息之间的关系。

    actorId总是覆盖参与者候选池中的actor(pooled actor)。所以如果任务实例既有actorId,也有参与者候选池的话,那么这个任务实例只出现在个人任务列表中。不过,参与者候选池有重要作用,一个用户可以将任务实例放回到组中重新供参与者候选人获取,这些只需要将任务实例的actorId属性去掉即可。

    任务实例变量

    任务实例可以拥有自己的一套变量,并且任务实例能够“看见”流程变量。任务实例是在一条执行路径token中创建的。需要注意的是:token和在这个token中创建的任务实例之间也是父子关系,这和token和其子token是父子关系是一样的。任务实例的变量和token相关的流程变量之间的范围规则同样适用。这意味着一个任务实例能够看到自己的一套变量,加上和它相关的token的所有变量。

    任务控制器能够用来在任务实例范围和流程范围变量之间传递和提交变量。

    任务控制器

     image

    任务控制器本质上是流程变量和用户界面程序的桥梁。从这张图片可以看出,任务是以任务实例的方式赋给特定的用户的,任务控制器任务实例负责流程变量和任务变量之间的传递。用户只能通过任务实例中的变量和流程打交道,因此用户界面接口中的输入输出都是流程变量<->任务控制器<->任务实例<->用户界面。因此实际上用户在界面中输入的都是以任务变量的形式保存,当任务结束后通过任务控制器来将任务变量转化为流程变量。

    最简单的情况下,流程变量和用户的表单参数是一对一的。任务控制器在任务元素中指定。JBPM提供一个默认的任务控制器,它维护一个变量元素列表。变量元素指示如何将流程变量拷贝到任务变量。

    <task name="clean ceiling">
      <controller>
        // name属性指的是流程变量的名字,mapped-name属性指的是任务变量的名字,如果没有设定mapped-name,则任务变量名和流程变量名一致
        // 需要注意的是:mapped-name任务变量名就是任务实例表单中字段的标签
        // access属性指定变量传递的形式,read指的是任务实例创建时,从流程变量中拷贝到任务变量
        // write指的是任务实例完成时,从任务变量中拷回到流程变量中,默认的是read,write
        <variable name="a" access="read" mapped-name="x" />
        <variable name="b" access="read,write,required" mapped-name="y" />
        <variable name="c" access="read,write" />
      </controller>
    </task>
    需要注意的是:一个任务节点可以有多个任务,而一个开始状态start-state只能有一个任务
    如果你觉得流程变量和表单参数一对一的关系无法满足需求的话,你也可以实现自己的任务控制器TaskControllerHandler。
    public interface TaskControllerHandler extends Serializable {
      void initializeTaskVariables(TaskInstance taskInstance, ContextInstance contextInstance, Token token);
      void submitTaskVariables(TaskInstance taskInstance, ContextInstance contextInstance, Token token);
    }
    <task name="clean ceiling">
      <controller class="com.yourcom.CleanCeilingTaskControllerHandler">
        // 此处配置实现的任务控制器
      </controller>
    </task>

    JBoss Seam 页面流释疑(1)

    Seam的页面流pageflow是很容易让人迷惑的。

    这里首先要弄清楚术语,Web程序通常都是在不同页面间来回跳转来实现应用逻辑的,所以通常的说法是页面导航Page Navigation, 这是最普遍和广泛的说法。因此对于页面导航Page Navigation的约定,被称为页面导航规则Page Navigation Rule。这几个术语是独立于语言和具体Web框架的。

    例如,在JSF开发中,我们使用outcome来实现页面导航。outcome本身就是一个字符串,我们通过某个方法返回的outcome字符串来判断下一步的逻辑步骤。当然这个工作需要通过页面和outcome字符串的匹配设置来完成。例如在Seam的猜数字应用中,我们也可以使用JSF的outcome来实现页面导航:

    <navigation-rule>
        <from-view-id>/numberGuess.jsp</from-view-id>
            
        <navigation-case>
            <from-outcome>guess</from-outcome>
            <to-view-id>/numberGuess.jsp</to-view-id>
            <redirect/>
        </navigation-case>
    
        <navigation-case>
            <from-outcome>win</from-outcome>
            <to-view-id>/win.jsp</to-view-id>
            <redirect/>
        </navigation-case>
            
        <navigation-case>
            <from-outcome>lose</from-outcome>
            <to-view-id>/lose.jsp</to-view-id>
            <redirect/>
        </navigation-case>
    
    </navigation-rule>

    我们可以看到JSF页面导航配置的三要素:

    1. 从哪个地方来:from-view-id指明从numberGuess.jsp页面来
    2. 某个可能的产出(outcome)是什么:from-outcome指明这个产出是win
    3. 这个可能的产出对应到哪里去:to-view-id指明如果产出是win的话,那么导向win.jsp页面

    但是你可能立刻就能发现这种处理方式的弊病来:

    1. 这种方式需要在代码中处理导航逻辑。尽管我们配置页面导航比较容易,但是实际上,我们增加了代码中的复杂性(有可能得不偿失啊)。
    2. 自由度太大。开发人员可以随意设置outcome的值和其导向页面这样当页面导航复杂度增加的时候管理起来非常困难。
    3. 约束性太小。这个缺点和第二个缺点有一定联系,约束性太小意味着,开发人员往往需要查看所有的页面文件*.jsp和整个配置文件navigation.xml才能明白全部的页面导航规则

    这种方式的页面导航被称为无状态的页面导航。

    需要强调的是:对于这种无状态的页面导航方式,除了JSF导航(使用navigation.xml)外,Seam还支持一种自己实现的无状态页面导航(使用pages.xml或者某个页面的xxx.page.xml):

    <page view-id="/admin/admin.xhtml">
            <navigation>
                <rule if-outcome="ship">
                    <redirect view-id="/admin/ship.xhtml"/>
                </rule>
                <rule if-outcome="accept">
                    <redirect view-id="/admin/accept.xhtml"/>
                </rule>
            </navigation>
        </page>

    这里Seam也使用了类似JSF的页面导航三要素:

    1. 从哪里来:page元素的view-id属性指明从/admin/admin.xhtml页面来
    2. 某个可能的产出(outcome): navigation元素中嵌套的rule元素的if-outcome属性指明可能的一个outcome是ship
    3. 到哪里去:rule元素中嵌套的redirect元素的view-id属性指明如果outcome属性是ship的话,导向ship.xhtml页面

    如果,在你的Seam应用程序中你准备使用无状态的页面导航的话,你有两种选择:

    1. 使用JSF导航:配置navigation.xml
    2. 使用Seam导航: 配置pages.xml或者某个xxx.page.xml

    我们将在后面的文章中比较这两种选择。

    Seam不仅仅只支持这种无状态的页面导航!更棒的是,Seam支持有状态的页面导航

    提醒:在Seam中,Seam实现的页面导航被成为页面流Pageflow

    1. JSF页面导航被称为页面导航,而页面导航是广泛的术语
    2. Seam实现的页面导航被成为页面流Pageflow
    3. 页面流可以是无状态的页面导航,Seam自己实现的无状态的页面导航
    4. 页面流也可以是有状态的页面导航,Seam通过JBPM实现的有状态的页面导航

    在我们讲解有状态的页面导航之前,我们先回顾一下无状态页面导航的弊病包括:虽然页面配置简单,但是代码中要控制页面导航规则;无法很容易的看清楚导航规则,因为约束小。

    Seam通过使用JBPM来帮助我们支持创建有状态的页面导航

    需要注意的是:Seam在两种层次上使用JBPM:

    1. 一种是在页面导航中使用JBPM;
    2. 另一种是在业务流程管理中使用JBPM.

    这两者的层次是不一样的,这也是容易导致Seam初学者迷惑的地方,在这里我们先弄清楚这一点:JBPM是独立的流程管理引擎,在Seam中它被使用在两个不同的地方:页面导航和流程管理中。

    实际上,Seam开发人员利用JBPM强大的定制功能,专门为页面导航定制了流程管理。Seam的使用者应该记住在Seam有状态的页面导航中page元素是JBPM节点Node的一种!这从侧面证明了JBPM强大的功能!

    OK, 我们接着说JBPM在Seam中的使用。

    首先看一下JBPM在页面导航中的作用:用来实现有状态的页面导航。

    JBPM在这里用来定义单个对话中的页面流。一个Seam对话指的是单个用户相对短时期内的交互

    我们再来看一下JBPM在流程管理中的作用:用来进行流程管理。

    业务流程可以跨越多个用户,多个对话。其流程被持久化到JBPM数据库中,所以它是长时期的。当然,管理多用户活动当然比管理单个用户活动复杂的多,包括复杂的任务管理和多路径并发管理。

    你可能在你的应用程序中同时在这两个地方都使用JBPM!

    OK,让我们回到有状态页面导航的主题上来。

    还是使用猜数字应用。这回我们需要用xxx.jpdl.xml文件来配置有状态的页面导航(pageflow.jpdl.xml):

    <pageflow-definition name="numberGuess">
        
       <start-page name="displayGuess" view-id="/numberGuess.jsp">
          <redirect/>
          <transition name="guess" to="evaluateGuess">
          	<action expression="#{numberGuess.guess}" />
          </transition>
       </start-page>
       
       <decision name="evaluateGuess" expression="#{numberGuess.correctGuess}">
          <transition name="true" to="win"/>
          <transition name="false" to="evaluateRemainingGuesses"/>
       </decision>
       
       <decision name="evaluateRemainingGuesses" expression="#{numberGuess.lastGuess}">
          <transition name="true" to="lose"/>
          <transition name="false" to="displayGuess"/>
       </decision>
       <process-state name="cheat">
          <sub-process name="cheat"/>
          <transition name="displayGuess"/>
       </process-state>
    
       <page name="win" view-id="/win.jsp">
          <redirect/>
          <end-conversation />
       </page>
       
       <page name="lose" view-id="/lose.jsp">
          <redirect/>
          <end-conversation />
       </page>
       
    </pageflow-definition>
    我们可以看到有状态的页面流是通过JBPM的JPDL(xxx.jpdl.xml)来配置的,几个要素:
    1. 页面流定义: pageflow-definition元素指明页面流名字
    2. 起始点:起始点包括两种可能的元素:start-page元素或者start-state元素(区别我们稍后介绍)指明页面流的起始点
    3. 页面:page元素指明页面流中的各个页面
    4. 转移:transition元素代替了无状态页面流的产出outcome作用,这里的转移将在具体页面中使用(例如在guessNumber.jspx文件中):<h:commandButton value="Guess" action="guess"/>,这个按钮激发的动作action名为guess, 而这个guess就是xxx.jpdl.xml文件中配置的一个转移(注意和无状态的页面流区别,这里的动作action指的是转移,而不是传统意义上的动作或方法调用)
    5. 动作:在转移元素中的action元素指明这个转移中发生的事件(和JBPM的转移的概念一致),还是上面的例子:<action expression="#{numberGuess.guess}" />,在xxx.jpdl.xml文件中转移guess中有个动作,这个动作是调用numberGuess对象的guess方法,并且这个转移guess将导向evaluateGuess这个决策。所以逻辑是这样的: 用户 -> 点击Guess按钮 ->Guess按钮的action激发guess转移 ->guess转移激发numberGuess对象的guess方法 -> guess转移导向evaluateGuess决策
    6. 决策:decision元素
    7. 流程状态:process-state元素包含子流程元素sub-process

    January 10

    JBoss Seam: Seam应用框架

    Seam Application Framework, 听起来挺吓人的,也很容易让人犯糊涂,Seam本身不就是个Web框架么,那里来的应用框架. 其实称之为Seam Component Template Framework更加合适. Seam 组件模板框架.

    原因很简单, 这个名词看起来吓人,其实就干一件事情, 把Seam组件开发再简化(尽管通过注释等方法, 开发Seam组件非常简单了). 怎么再简化呢? 大家可以想一想Struts的Dynamic Action历史就明白了. 也就是通过配置来创建基本的Seam组件, 你可以不用写类了! 在componets.xml文件中配置.例如下面的配置信息能够创建一个基本的实现CRUD功能的Seam组件:

    <framework:entity-home name="personHome" 
                           entity-class="eg.Person" 
                           entity-manager="#{personDatabase}">
        <framework:id>#{param.personId}</framework:id>
    </framework:entity-home>
    如果你讨厌通过XML配置来替代编程的话(反正我讨厌), 你也可以通过类继承来实现:
    @Stateful
    @Name("personHome")
    public class PersonHome extends EntityHome<Person> implements LocalPersonHome {
        @RequestParameter String personId;
        @In EntityManager personDatabase;
        
        public Object getId() { return personId; }
        public EntityManager getEntityManager() { return personDatabase; }
        
    }

    一头雾水吧?让我们看看Seam的代码.所有的Application Framework代码位于org.jboss.seam.framework下面.

    最重要的两个类为Home类和Query类.

    Home类: 提供了实体类的持久化操作CRUD. Seam提供两种实现:EntityHome和HibernateEntityHome. 一个是给JPA的,一个是给Hibernate的.

    Query类: 顾名思义,提供实体类查询的.同样,Seam提供两种实现: EntityQuery, HibernateEntityQuery.一个是给JPA的,一个是给Hibernate的.

    Home类和Query类天生就能和会话范围,事件范围和对话范围一起工作.

    需要注意的是:Seam Application Framework只能和Seam管理的持久化上下文一起工作,Seam-managed Persistence Context.

    默认的情况下,Application Framework的组件查找一个名为entityManager的持久化上下文.

    来点仔细的. 我们看一下Home, 其实Home就是给某个实体类干CRUD活的,类似于EJB3的Session Bean, 比如说我们有个实体类Person, 那么PersonHome就是创建,修改,删除Person的工具么.

    @Entity
    public class Person { // 这个是我们的实体类
        @Id private Long id;
        private String firstName;
        private String lastName;
        private Country nationality;
        
        //getters and setters...
    }
    1. 可以通过在components.xml配置相对应的Home类:
    <framework:entity-home name="personHome" entity-class="eg.Person" />
    2. 或者自己写类来继承
    @Name("personHome")
    public class PersonHome extends EntityHome<Person> {} // 参数是实体类啊

    反正我觉的第二种方法最合适,如果你是个程序员的话.

    然后你就自动获得PersonHome类了, 内置persist(), remove(), update()和getInstance()方法. 酷吧!当然,你要调用remove()或update()方法前,要先设置setId(Object id)方法.

    OK, 那么怎么使用呢, 我们看一个例子, 我们可以直接在页面上使用Home类:

    <h1>Create Person</h1>
    <h:form>
        <div>First name: <h:inputText value="#{personHome.instance.firstName}"/></div>
        <div>Last name: <h:inputText value="#{personHome.instance.lastName}"/></div>
        <div>
            <h:commandButton value="Create Person" action="#{personHome.persist}"/>
        </div>
    </h:form>

    用户在页面上的输入直接就能创建一个Person实体对象了,当然,通过PersonHome创建的.不过我们会觉得使用personHome.instance很不爽, 其实我们更加愿意使用person来代替它. 做到这一点也很容易:

    1. 在components.xml中配置

    <factory name="person" 
             value="#{personHome.instance}"/>
    
    <framework:entity-home name="personHome" 
                           entity-class="eg.Person" />
    2.在自己写的类中加一个方法,然后用@Factory注释
    @Name("personHome")
    public class PersonHome extends EntityHome<Person> {
        // 很简单,这个方法仅仅调用的getInstance()而已
        @Factory("person")
        public Person initPerson() { return getInstance(); }
        
    }
    OK, 现在我们的页面就可以变成:
    <h1>Create Person</h1>
    <h:form>
        <div>First name: <h:inputText value="#{person.firstName}"/></div>
        <div>Last name: <h:inputText value="#{person.lastName}"/></div>
        <div>                                              // 这里没变
            <h:commandButton value="Create Person" action="#{personHome.persist}"/>
        </div>
    </h:form>
    相信你已经很满意了吧.那么,也许你想搞个页面来编辑某个已经持久化的Person实体,那么你需要在pages.xml中配置页面参数:
    <pages>
        <page view-id="/editPerson.jsp"> // 编辑页面需要person id参数
            <param name="personId" value="#{personHome.id}"/>
        </page>
    </pages>
    让我们看看编辑页面:
    <h1> // 如果personHome还没有管理某个person实体的话, 页面显示为 创建Person, 否则显示 编辑Person
         // 此时,页面参数personId 为空
        <h:outputText rendered="#{!personHome.managed}" value="Create Person"/>
        <h:outputText rendered="#{personHome.managed}" value="Edit Person"/>
    </h1>
    <h:form>
        <div>First name: <h:inputText value="#{person.firstName}"/></div>
        <div>Last name: <h:inputText value="#{person.lastName}"/></div>
        <div>
            //如果personHome还没有管理某个person实体的话,按钮显示创建,否则的话显示两个按钮 更新和删除
            // 此时,页面参数personId 为空
            <h:commandButton value="Create Person" action="#{personHome.persist}" rendered="#{!personHome.managed}"/>
            <h:commandButton value="Update Person" action="#{personHome.update}" rendered="#{personHome.managed}"/>
            <h:commandButton value="Delete Person" action="#{personHome.remove}" rendered="#{personHome.managed}"/>
        </div>
    </h:form>
    关于Home类,我们先讲这么多,再来看看Query类.
    Query类通常用来查询一个实体对象的列表.比如获得所有Person:
    1.通过components.xml配置
    <framework:entity-query name="people" 
                            ejbql="select p from Person p"/>
    使用页面:
    <h1>List of people</h1>
    <h:dataTable value="#{people.resultList}" var="person">
        <h:column>
            <s:link view="/editPerson.jsp" value="#{person.firstName} #{person.lastName}">
                <f:param name="personId" value="#{person.id}"/>
            </s:link>
        </h:column>
    </h:dataTable>
    要是我们想分页看呢:
    <framework:entity-query name="people" 
                            ejbql="select p from Person p" 
                            order="lastName"  // 排序
                            max-results="20"/> // 分页最大结果
    在pages.xml配置:
    <pages>
        <page view-id="/searchPerson.jsp">
            <param name="firstResult" value="#{people.firstResult}"/>
        </page>
    </pages>
    更改后的页面:
    <h1>Search for people</h1>
    <h:dataTable value="#{people.resultList}" var="person">
        <h:column>
            <s:link view="/editPerson.jsp" value="#{person.firstName} #{person.lastName}">
                <f:param name="personId" value="#{person.id}"/>
            </s:link>
        </h:column>
    </h:dataTable>
    
    <s:link view="/search.xhtml" rendered="#{people.previousExists}" value="First Page">
        <f:param name="firstResult" value="0"/> // <<
    </s:link>
    
    <s:link view="/search.xhtml" rendered="#{people.previousExists}" value="Previous Page">
        <f:param name="firstResult" value="#{people.previousFirstResult}"/>
    </s:link> 
    <s:link view="/search.xhtml" rendered="#{people.nextExists}" value="Next Page">
        <f:param name="firstResult" value="#{people.nextFirstResult}"/>
    </s:link> 
    <s:link view="/search.xhtml" rendered="#{people.nextExists}" value="Last Page">
        <f:param name="firstResult" value="#{people.lastFirstResult}"/>
    </s:link>
    我们甚至可以监听一些事件,例如更新,删除等等然后根据这些事件,刷新查询:
    <event type="org.jboss.seam.afterTransactionSuccess">
        <action execute="#{people.refresh}" />
    </event>
    甚至可以限定是Person实体的事件:
    <event type="org.jboss.seam.afterTransactionSuccess.Person">
        <action execute="#{people.refresh}" />
    </event>

    需要注意的是:上面的Query例子都用的是配置形式,你也可以写自己的类来继承.Seam通常将Query的继承类命名为XXXList, 将Home类命名为XXXHome, 其中XXX是实体类.

    有趣的Controller类其实就是一些实体类的帮助类,让你更加容易使用内置组件和内置组件的方法.

            like   session bean                          |                           like session bean

    XXXList/Query(负责XXX的查询)  <----  你的实体类XXX  ----> XXXHome (负责XXX的CRUD工作)

                                                                |

                                                       Controller(XXX的帮助类)

    December 21

    JBPM:事务的划分

    JBPM是在客户端线程中运行的流程,因此它自然就是同步的。这意味着token.signal()和taskInstance.end()方法只有在流程执行进入了一个新的等待状态才能返回。

    大部分情况下,这是非常直观的方法,因为流程执行能够很容易的和服务器端事务绑定:在同一个事务中流程从一个状态执行到另一个状态。

    在流程中需要大量时间计算的场景中,这种方法肯定不是想要的。为了解决这种场景遇到的问题,JBPM引入了一个异步消息系统,允许流程以异步的方式执行。当然,在Java企业环境中,JBPM可以被配置为使用其他的JMS消息代理系统,而不是使用内置的消息系统。

    在任何节点中,JPDL支持async属性为true。被配置为异步的节点将不会在客户端线程中执行。相反,一个消息将被异步消息系统发送,然后线程就被返回给客户端(这意味着token.signal()和taskInstance.end()将被返回)。

    注意,这时候JBPM客户端代码可以提交事务了。在流程更新的同一个事务中消息将被发送。所以事务的结果就是:token被移到了下一个节点(这时这个节点还没有被执行),并且一个执行节点命令(ExecuteNodeCommand)消息被异步消息系统发送给JBPM的命令执行器Command Executor。

    JBPM的命令执行器从消息队列中读取并且执行。在执行节点命令中,流程将被继续执行。每个命令都在一个单独的事务中被执行。

    所以为了让异步的流程能够继续,一个JBPM命令执行器必须运行。最简单的方法是在Web应用中配置CommandExecutionServlet。让这个Servlet负责运行JBPM命令执行器。或者你使用其他方法,总之必须保证命令执行器线程必须运行。

    作为一个流程的建模者,你不必关心所有的异步消息,关键一点是事务的划分:默认情况下,JBPM在客户端操作事务,计算整个流程直到流程进入一个等待状态。你可以将async属性设为true去在流程中划分事务。

    <start-state>
      <transition to="one" />
    </start-state>
    <node async="true" name="one"> // 异步
      <action class="com...MyAutomaticAction" />
      <transition to="two" />
    </node>
    <node async="true" name="two"> // 异步
      <action class="com...MyAutomaticAction" />
      <transition to="three" />
    </node>
    <node async="true" name="three"> // 异步
      <action class="com...MyAutomaticAction" />
      <transition to="end" />
    </node>
    <end-state name="end" />
    客户端代码与流程执行的交互(启动,继续启动)和同步的一样处理:
    ...start a transaction...
    JbpmContext jbpmContext = jbpmConfiguration.createContext();
    try {
      ProcessInstance processInstance = jbpmContext.newProcessInstance("my async process");
      processInstance.signal();
      jbpmContext.save(processInstance);
    } finally {
      jbpmContext.close();
    }
    在上述例子中,第一个事务结束后,流程执行的根令牌root token将会指向节点one,然后一个执行节点命令消息ExecutionNodeCommandMessage
    被发送给命令执行器。在接下来的事务中,命令执行器将会读取消息队列的消息,然后执行节点one。这个动作能够决定是传递流程执行还是进入等待状态。
    如果动作决定传递流程执行,那么在流程执行到达节点two的时候,事务就结束了。然后继续...继续...下去。

    JBPM:图的执行Graph Execution

    JBPM中,图的执行模型是基于对流程定义的解释和命令模式链的。

    对流程定义的解释意味这流程定义必须存储在数据库中(其实不尽然,但必须有某种存储机制)。在流程执行过程中需要流程定义的信息。需要注意的是JBPM使用hibernate的二级缓存来避免在运行时加载流程定义信息。这是因为一般来说流程定义不会轻易改变,所以我们能够将流程定义放入缓存中。

    命令模式链意味着图中的每个节点需要负责将流程的执行传递下去。如果某个节点没有传递流程的执行,那么这个节点位于等待状态。

    JBPM的设计思想是在流程实例上启动执行,然后这个流程执行将一直继续下去直到进入一个等待状态。

    一个令牌token代表一个执行的路径。一个token拥有一个指向流程执行中的一个节点的指针。同样token也可以通过数据库将自己持久化。在等待状态时,token可以被持久化到数据库中。我们看一下JBPM是如何计算token的执行的。当有一个信号singal发送给token的时候,执行开始启动,接着执行通过命令模式链的方式沿着转移transition和节点node传递。在类定义中有相关的方法:

    image

    当token在节点中的时候,信号可以发送给token.信号的发送指令着执行的启动。一个信号必须指定当前节点中token的离开转移(节点到转移被称为离开转移,转移到节点被称为到达转移) 。

    需要注意的是第一个转移就是默认的转移。

    给token发送信号的时候,token会调用在其内维护的当前节点的Node.leave(ExecutionContext,Transition)方法。你可以将ExecutionContext想象成是一个token(会引起误导,不过ExecutionContext中维护着一个token).这个方法(Node.leave())能够触发一个离开节点的事件(node-leave类型),并且调用转移transition的take方法Transition.take(ExecutionContext).

    这个方法(Transition.take())能够触发一个transition的事件并且调用这个转移Transition的目的节点的enter方法(Node.enter())(每个Transition都维护一个from节点和一个to节点,from节点被称为源节点,to节点被称为目的节点)。

    每种类型的节点都在execute方法中有自己的行为。每个节点都通过Node.leave(ExecuteContext,Transition)来负责传递流程的执行。

    简单的说:

    • Token.signal(Transition)
    • --> Node.leave(ExecutionContext,Transition)
    • --> Transition.take(ExecutionContext)
    • --> Node.enter(ExecutionContext)
    • --> Node.execute(ExecutionContext)

    需要注意的是整个的下一状态的计算(包括动作的调用)都是在客户端线程中完成的。一个常见的错误理解是所有的计算都必须在客户端线程中完成。对于任何的异步调用,你可以使用异步消息JMS。当消息在流程实例更新的同一个事务中被发送时,所有的同步问题都要小心的处理。一些工作流系统在图中的所有节点间使用的是异步消息。但是在高通量环境中,这种算法能够为业务流程提供更高的灵活性和控制性。

    December 20

    JBPM术语:动作Action

    概述

    动作是在流程执行中的事件中的Java代码片段。你可以认为动作是可以在事件上发生的(注意,也可以在节点内发生)。为何要有动作这个概念呢?原因在于图虽然是软件需求间通讯的重要设备,但是图只是软件的一个视图或者投影,它隐藏了太多技术细节(流程图主要给业务分析员看或者编辑的),而动作是给流程图增加技术细节的机制(开发者需要用到它)。我们需要动作来装点图,这样的图才有意义。

    因为动作多少和事件有关系,我们看一下事件类型。主要的事件类型包括:进入节点,离开节点,走向转移

    注意:动作可以在事件中发生,也可以在节点中发生!而这两种情况非常不同!

    • 在事件中的动作当事件触发时执行,事件中的动作无法影响流程的控制,有点象观察者模式,动作是事件的监听器
    • 在节点中的动作必须负责传递流程的执行,因此能够影响流程的控制

    image

    我们看一下事件中动作的例子,假定我们需要在一个指定的转移中更新数据库,更新数据库是技术细节,业务分析员不关心这个。这个例子是如果开除一个员工,我们需要将他的工作证收回,在这个转移中,需要更新人力资源的数据库。

    public class RemoveEmployeeUpdate implements ActionHandler { // 实现ActionHandler
      public void execute(ExecutionContext ctx) throws Exception {
        // 从流程变量中获得开除的员工
        String firedEmployee = (String) ctx.getContextInstance().getVariable("fired employee");
        
        // by taking the same database connection as used for the jbpm updates, we 
        // 重用JBPM的事务来更新数据库
        Connection connection = ctx.getProcessInstance().getJbpmSession().getSession().getConnection();
        Statement statement = connection.createStatement();
        statement.execute("DELETE FROM EMPLOYEE WHERE ...");
        statement.execute(); 
        statement.close();
      }
    }
    <process-definition name="yearly evaluation">
    
      ...
      <state name="fire employee">
        <transition to="collect badge">
          <action class="com.nomercy.hr.RemoveEmployeeUpdate" />
        </transition>
      </state>
      
      <state name="collect badge">
      ...
      
    </process-definition>
    在这个例子中,我们实现了ActionHandler,同时将这个类作为动作放在名为收回工作证的转移中。

    JBPM术语:节点Node和节点类型NodeType

    概述

    流程由节点和转移组成。每个节点都有特定的类型。节点的类型决定了当一个流程执行在运行时到达这个节点后将发生什么。JBPM已经具有多个预定义的节点类型。当然你可以定制自己的节点从而实现特殊需求的节点行为。那么节点负责什么呢?

    节点主要负责两件事情:

    1. 执行普通的Java代码。通常这些Java代码和节点的功能相关。例如,创建一些任务实例,发送一些通知,更新一下数据库,等等
    2. 传递流程的执行process execution。

    传递流程的执行

    每个节点都有以下几种方式传递流程的执行:

    • 不传递流程的执行。如果该节点不传递流程的执行,那么该节点就是一个等待状态。
    • 将流程的执行传递给节点的其中一个转移。这意味着到达这个节点的token将被传递给这个节点的其中一个转移。这个过程是通过调用executionContext.leaveNode(String)来实现的。这意味这个节点能够执行一些定制的程序逻辑,然后自动的继续流程的执行,而无需等待,可以称之为自动化节点。
    • 创建流程执行的新路径。一个节点可以创建新的token.每个新的token都代表流程执行的一个新路径,每个token都可以被送到节点的转移中。这种节点的例子是分叉fork节点。
    • 终结流程执行的路径。一个节点可以决定终结流程执行的路径。这意味这token终止了,这个流程路径结束了。
    • 最常见的是,一个节点可以修改流程实例的整个运行结构。运行结构是包含一个token树的流程实例。每个token都代表一个流程执行的路径.一个节点能够创建和终止token,能够将每个token放入图的节点中,或者将token发送给转移。

    节点的类型

    JBPM包含了很多预定义和实现的节点类型,这包括:

    1. 任务节点 task-node, 任务节点类型的节点代表一个或多个人工操作的任务,当一个执行到达一个任务节点的时候,工作流参与者的任务列表中就会有任务实例被创建,然后这个节点将会作为一个等待状态,等待参与者操作任务,任务结束后将会触发流程执行的重新启动,换句话说,任务结束将会给token一个信号,让其激活,继续执行。
    2. 状态 state, 状态类型的节点本质上说就是等待状态。状态类型的节点和任务节点类型的区别在于流程执行到达状态类型的节点的时候,没有任何任务实例被创建。这在流程等待外部系统交互的时候非常有用。流程执行进入状态类型的节点后,将会一直等待,直到外部系统发送回一个响应信息,接着token.signal()方法会被调用,然后触发流程执行继续下去。
    3. 决策 decision, 决策
    4. 分叉 fork
    5. 合并 join
    6. 节点 Node(很奇怪不是么,有一个节点类型为节点的节点), 这种类型的节点主要是当你想要在节点中编写自己的代码的时候才用到的。这个类型为节点的节点有一个Action子元素,当流程执行到达这个节点后,这个Action将被执行。你在动作控制器ActionHandler中编写的代码能够做任何事情,但是它仍然需要负责流程执行的传递。当你希望使用Java API去实现一些对于业务分析员非常重要的业务逻辑的时候这种类型的节点非常有用。这种节点是能够在流程的图表示中出现的。那动作Action作比较,如果你使用动作action那么你添加的代码在流程的图表示中是不可见的。当你觉得逻辑对于业务分析员不重要的时候你可以将代码编写到动作action中。

    JBPM术语:图的元素GraphElement

    概念

    GraphElement是JBPM的一个定义,位于org.jbpm.graph.def包中。GrahpElement是个抽象类。

    GraphElement有一个名字name, 一个描述description.

    GraphElement有多个事件event,使用Map来存储。

    GraphElement有多个例外处理器exceptionhandler, 使用List来存储。

    GraphElement有一个流程定义processDefinition. 有意思的是:processDefinition本身就是GraphElement的子类。

    子类

    • Node 节点
    • Transition 转移
    • ProcessDefinition 流程定义
    • Task 任务

    重要的方法

    • executeAction(Action ation, ExecutionContext excContext)
    • fireEvent(String eventType, ExcecutionContext, exeContext)
    • fireAndPropagateEvent(String eventType, ExcecutionContext exeContext)

    JBPM术语定义:令牌token

    概念

    token是执行execution的一条路径, 它是运行时的概念,维护一个指向流程定义中节点的指针。

    我们通常通过ProcessInstance.getRootToken()或ProcessInstance.findToken(String token)来获得token。

    每个token可以有一个父token, 这意味着这个token可以由父token产生出来,这主要考虑的是流程可以分叉fork,因此每个分支都要有新的token来代表各自的执行路径。当然每个token都可以有多个子token,使用Map来存储它们。

    每个token都可以有多个注释comment,使用List来存储它们。

    每个token都有一个流程实例ProcessInstance, 也有一个子流程实例Sub ProcessInstance

    每个token都有一个节点Node, 表示当前节点

    原理

    信号signal用来指引一个令牌token去继续一个图的执行。当token接受到一个无名的信号时,它将离开当前的节点(注意包括节点的子类),走向默认的转移transition, 要是信号有参数指定转移的话,走向该指定的转移。注意在流程实例ProcessInstance中信号signal是委派给根令牌root token的!因此对流程实例的信号实际就是根令牌的信号。

    当token进入一个节点后(token维护一个Node),这个节点将被执行。注意节点node自身负责图的继续执行,当这个token离开这个节点就意味着这个节点中的图的继续执行已经完成了。每种节点类型node type都可以实现自己特定的图的继续执行。

    如果一个节点node并不传递执行,说明这个节点是一个状态

    重要的方法

    • get/setNode(Node node)
    • signal(), signal(String transitionName), signal(Transition transition)

    token.signal()方法能够激活token本身,然后让token离开当前状态,走向默认的转移transition

    带参数的signal()方法让token走向指定的转移transition

    • suspend()

    token.suspend()方法能够将token暂停挂起

    • resume()

    token.resume()方法能够将token重新启动

    December 18

    JBoss Seam事件机制(3):页面动作

    在JBoss Seam事件机制(1)概述中讲到,Seam中的页面动作发生在页面渲染之前,我们在WEB-INF/pages.xml文件中配置页面动作。我们还提到了page元素中的view-id不一定非要是JSP或者Facelet页面,这给整合其他WEB框架留了空间,并且能够让我们处理非JSF的请求。另外页面动作可以返回一个JSF输出,通过JSF输出来定制导向。除此之外,在page元素中,我们可以使用多个action元素来完成有条件的页面动作:

    <pages>
        <page view-id="/hello.jsp">
            <action execute="#{helloWorld.sayHello}" if="#{not validation.failed}"/>
            <action execute="#{hitCount.increment}"/>
        </page>
    </pages>
    页面参数
    我们知道一个JSF请求(表单提交)包括(封装)了动作和参数,所以一个页面动作也许需要参数。使用GET的请求可以做书签,因为
    页面参数都作为可读的请求参数处理了。但是JSF表单使用的是POST。Seam能够让我们将请求参数和模型属性相关联和映射:
    <pages>
          <page view-id="/hello.jsp" action="#{helloWorld.sayHello}">
              <param name="firstName" value="#{person.firstName}"/>
              <param name="lastName" value="#{person.lastName}"/>
          </page>
      </pages>
    page元素中的param参数是双向的,就像JSF输入的值绑定一样。
    • 当一个非Faces请求(GET)请求某个视图view-id的时候,Seam经过正确的类型转换后将请求的参数值赋给模型对象.

    • 任何<s:link> 或者 <s:button> 都显式得包含请求参数。参数值由渲染阶段(<s:link>渲染)的值绑定计算结果来决定.

    • 请求某个视图view-id的任何带有<redirect/>的导向规则都显式得包含请求参数 . 参数值由调用应用阶段结束时的值绑定计算来决定.

    • 值通过任何对给定视图view-id的页面的JSF表单提交来传递. 这意味着视图参数有些类似于Faces请求中页面范围的上下文变量。

    关键的思想是我们如何从另一个页面到/hello.jsp(或者从/hello.jsp又回到/hello.jsp), 在值绑定中的模型的值仍然被记住,而不需要任何的对话或者其他的服务器端的状态。

    请求值的传递

    如果只是name属性被指定的话,请求参数使用PAGE页面上下文(没有和模型属性映射)来传递。

    <pages>
          <page view-id="/hello.jsp" action="#{helloWorld.sayHello}">
              <param name="firstName" /> // 没有value
              <param name="lastName" />
          </page>
    </pages>
    如果你想创建多层次主从模式的CRUD页面时,页面参数的传递就有必要了。你可以通过页面参数的传递来记住先前你在哪个视图上
    (例如你在保存页面上,必须记住上个页面你正在编辑的实体)
    • 如果参数以视图的页面参数列出的情况下,任何<s:link> 或者 <s:button> 显式的传递请求参数.

    • 值通过任何对给定视图view-id的页面的JSF表单提交来传递. 这意味着视图参数有些类似于Faces请求中页面范围的上下文变量。

    听起来太复杂了,这有必要么?当你用它的时候你就知道了。这值得下功夫去理解。页面参数是在非Faces请求间传递状态的最佳方式。如果你想让你的搜索结果页面也能做书签的话,我们就能够在同一个代码中使用它来处理POST和GET两种请求了。页面参数能够减少视图定义中请求参数列表的重复性,并且让在代码中处理重定向更加简单。

    对话和验证

    导向

    在Seam中,你可以使用JSF的faces-config.xml文件来设置标准的JSF向导规则。但是JSF的向导规则有很多缺点:

    • 在重定向的时候无法指定使用的请求参数.

    • 无法根据规则来启动或者结束对话.

    • 规则是根据动作方法返回的值来工作的,无法通过EL表达式来计算.

    还有一个问题就是如果在Seam中使用标准的JSF导向,业务逻辑将散落在pages.xml和faces-config.xml两个配置文件中,很散乱。因此建议只是用pages.xml来配置页面导向

    我们看一下如何转换:

    <navigation-rule> // JSF标准导向,在faces-config.xml中配置
        <from-view-id>/editDocument.xhtml</from-view-id>
        
        <navigation-case>
            <from-action>#{documentEditor.update}</from-action>
            <from-outcome>success</from-outcome>
            <to-view-id>/viewDocument.xhtml</to-view-id>
            <redirect/>
        </navigation-case>
        
    </navigation-rule>
    <page view-id="/editDocument.xhtml"> // 等价的Seam导向,在pages.xml中配置
        
        <navigation from-action="#{documentEditor.update}">
            <rule if-outcome="success">
                <redirect view-id="/viewDocument.xhtml"/>
            </rule>
        </navigation>
        
    </page>
    <page view-id="/editDocument.xhtml"> // 改良版1, 甚至不使用JSF返回的string
        
        <navigation from-action="#{documentEditor.update}" 
                       evaluate="#{documentEditor.errors.size}">
            <rule if-outcome="0"> // 如果错误数量为0,就代表OK
                <redirect view-id="/viewDocument.xhtml"/>
            </rule>
        </navigation>
        
    </page>
    <page view-id="/editDocument.xhtml"> // 改良版2,判断错误是否为空
        
        <navigation from-action="#{documentEditor.update}">
            <rule if="#{documentEditor.errors.empty}"> 
                <redirect view-id="/viewDocument.xhtml"/>
            </rule>
        </navigation>
    </page>
    <page view-id="/editDocument.xhtml"> // 如果你希望编辑后,结束对话
        
        <navigation from-action="#{documentEditor.update}">
            <rule if="#{documentEditor.errors.empty}">
                <end-conversation/>
                <redirect view-id="/viewDocument.xhtml"/>
            </rule>
        </navigation>
        
    </page>
    <page view-id="/editDocument.xhtml"> // 改良版3,或者你需要将参数传递下去
        // 如果我们结束了对话,后续的请求无法知道哪个document我们感兴趣,我们需要将document id作为参数传递
        <navigation from-action="#{documentEditor.update}">
            <rule if="#{documentEditor.errors.empty}">
                <end-conversation/>
                <redirect view-id="/viewDocument.xhtml">
                    <param name="documentId" value="#{documentEditor.documentId}"/>
                </redirect>
            </rule>
        </navigation>
        
    </page>
    值得注意的是:JSF对于输出返回为Null的采用特殊处理,如果返回为Null的话指向原页面(重新显示页面)。但在Seam中不太一样。
    <page view-id="/editDocument.xhtml">
        
        <navigation from-action="#{documentEditor.update}">
            <rule> // 这里的规则对非Null输出是有效的,但是对Null输出无效
                <render view-id="/viewDocument.xhtml"/>
            </rule>
        </navigation>
        
    </page>
    需要变成:
    <page view-id="/editDocument.xhtml">
        // 去掉rule元素
        <navigation from-action="#{documentEditor.update}">
            <render view-id="/viewDocument.xhtml"/>
        </navigation>
        
    </page>
    <page view-id="/editDocument.xhtml">
        // view-id也可以使用EL表达式指定
        <navigation if-outcome="success">
            <redirect view-id="/#{userAgent}/displayDocument.xhtml"/>
        </navigation>
        
    </page>
    细粒度的定义导向,页面动作和参数
    如果你有很多页面动作和参数,或者有很多导向规则,你可能想使用多个文件来配置。这时候你可以使用view-id来创建响应的page.xml
    文件。例如你有个view-id为/calc/calculator.jsp的页面,你可以创建calc/calculator.page.xml文件来配置,内容如下:
     
    <page action="#{calculator.calculate}"> // 使用page元素作为根元素
        <param name="x" value="#{calculator.lhs}"/> // 指定参数
        <param name="y" value="#{calculator.rhs}"/>
        <param name="op" converter="#{operatorConverter}" value="#{calculator.op}"/>
    </page>

    JBoss Seam的事件机制(2):内置的上下文事件

    在JBoss Seam的事件机制(1)概述中我们提到Seam提供了内置的上下文事件,我们可以为每个事件定制自己的监听器:

    关于验证的事件

    • org.jboss.seam.validationFailed, 当JSF验证失败的时候被调用

    关于变量的事件

    • org.jboss.seam.preSetVariable.<name>
    • org.jboss.seam.postSetVariable.<name>
    • org.jboss.seam.preRemoveVariable.<name>
    • org.jboss.seam.postRemoveVariable.<name>

    关于上下文的事件

    • org.jboss.seam.preDestroyContext.<SCOPE>
    • org.jboss.seam.postDestroyContext.<SCOPE>

    关于对话的事件

    • org.jboss.seam.beginConversation
    • org.jboss.seam.noConversation,当需要长时间运行的对话,缺没有的时候被调用
    • org.jboss.seam.endConversation

    关于页面流的事件

    • org.jboss.seam.beginPageflow.<name>
    • org.jboss.seam.endPageflow.<name>

    关于流程和任务的事件

    • org.jboss.seam.createProcess.<name>
    • org.jboss.seam.endProcess.<name>
    • org.jboss.seam.initProcess.<name>
    • org.jboss.seam.startTask.<name>
    • org.jboss.seam.endTask.<name>
    • org.jboss.seam.initTask.<name>

    关于组件的事件

    • org.jboss.seam.postCreate.<name>
    • org.jboss.seam.preDestroy.<name>

    关于JSF生命周期的事件

    • org.jboss.seam.beforePhase
    • org.jboss.seam.afterPhase
    • org.jboss.seam.postInitialization

    关于认证和安全的事件

    • org.jboss.seam.preAuthenticate.<name>
    • org.jboss.seam.postAuthenticate.<name>
    • org.jboss.seam.notLoggedIn
    • org.jboss.seam.remeberMe 如果Seam在客户端Cookie中发现username时发生

    关于例外的事件

    • org.jboss.seam.exceptionHandled.<type>
    • org.jboss.seam.exceptionHandled
    • org.jboss.seam.exceptionNotHandled

    关于事务的事件

    • org.jboss.seam.afterTransactionSucess.<name>
    • org.jboss.seam.afterTransactionSucess

    JBoss Seam的事件机制(1):概述

    JBoss Seam如何做到松耦合的架构呢?光有具备上下文的组件模型是不够的。还必须有:

    1. 事件模型。事件模型通过事件<-->监听器模式来构建。采用的方式是象JSF方法绑定一样的方式实现。
    2. 使用注释和拦截器来纵向切入实现业务逻辑的组件,从而达到松耦合的事件触发和响应

    JBoss Seam的组件模型本身就是为事件驱动的应用来设计的。这些事件都是通过JSF的表达式语言的方法绑定来映射的。在JBoss Seam中,事件可以分为:

    1. JSF事件,例如:
    2. <h:commandButton value="Click me!" action="#{helloWorld.sayHello}"/> JSF的按钮绑定动作
    3. jBPM状态转移事件,例如:
    4. <start-page name="hello" view-id="/hello.jsp">
          <transition to="hello">
              <action expression="#{helloWorld.sayHello}"/>
          </transition>
      </start-page> jBPM流程或页面流定义
    5. Seam页面动作,页面事件发生在我们渲染一个页面之前,我们通过在WEB-INF/pages.xml文件中配置页面动作。我们可以为一个特定的JSF视图ID (View ID)设定一个页面动作,例如:
    6. <pages>
          <page view-id="/hello/hello.jsp" action="#{helloWorld.sayHello1}"/>
      </pages>
      我们也可以使用通配符为一个页面模式设定页面动作,例如:
      <pages>
          <page view-id="/hello/*" action="#{helloWorld.sayHello2}"/>
      </pages>
      如果多个页面动作匹配当前的页面视图的话,那么所有动作按照最窄到最宽泛的顺序依次调用
      例如上述sayHello1,sayHello2动作都和hello.jsp匹配,那么在hello.jsp页面上先调用
      sayHello1然后调用sayHello2。页面动作可以返回一个JSF的输出outcome,如果outcome
      不为null的话,Seam将使用定义好的规则去导向到一个视图。更棒的是page元素中的视图
      view-id可以不是JSP页面或者Facelet页面,允许我们通过view-id来整合基于动作的Struts
      或者Webwork框架。你可以使用action元素指定多个条件页面动作(在某种条件下才执行的动作)
      例如:
      <pages>
          <page view-id="/hello.jsp">
              <action execute="#{helloWorld.sayHello}" if="#{not validation.failed}"/>
              <action execute="#{hitCount.increment}"/>
          </page>
      </pages>
       
    7. Seam组件驱动的事件,Seam组件之间可以直接通过方法调用来相互沟通。有状态的组件可以实现观察者模式。虽然如此,Seam提供的组件驱动的事件比直接通过方法调用更加松耦合。我们来看一下如何设置组件驱动的事件。我们可以在components.xml文件中设置事件监听器(观察者):
    8. <components>
          <event type="hello"> // 设置事件, Observable
              <action execute="#{helloListener.sayHelloBack}"/> // 设置监听器,Observer
              <action execute="#{logger.logHello}"/> // 设置监听器,Observer
          </event>
      </components> 你可能想问事件类型type="hello"是什么,它只是一个任意的字串。事件类型将在组件驱动事件的时候使用
      (raiseEvent)。当事件发生时,在事件中注册的动作将根据在components.xml文件中的次序依次调用。你可能要问:我如何
      在组件中触发一个事件呢?基本上,你有两种选择:使用内置的组件和通过注释。下列代码在运行HelloWord的sayHello方法时
      触发事件类型为"hello"的事件(在components.xml文件中配置):
      @Name("helloWorld")
      public class HelloWorld {
          public void sayHello() {
              FacesMessages.instance().add("Hello World!");
              Events.instance().raiseEvent("hello"); // Events 内置组件 触发hello事件
          }
      }
      @Name("helloWorld")
      public class HelloWorld {
          @RaiseEvent("hello") // 注释 触发hello事件
          public void sayHello() {
              FacesMessages.instance().add("Hello World!");
          }
      }
      我们看到HelloWorld的sayHello方法其实是事件的产生者。值得注意的是事件的产生者和事件的消费者
      (监听者)没有任何依赖性,例如下面的hello监听器和上述的HelloWord没有依赖:
      @Name("helloListener") // 在components.xml文件的hello事件中注册的监听者
      public class HelloListener {
          public void sayHelloBack() { // 当hello事件触发后,监听者调用该监听方法
              FacesMessages.instance().add("Hello to you too!");
          }
      }
      注意:如果你讨厌在components.xml文件配置太多事件和监听器,那么你也可以使用注释来配置:
      @Name("helloListener")
      public class HelloListener {
          @Observer("hello") // 指明使用sayHelloBack方法来监听hello事件
          public void sayHelloBack() {
              FacesMessages.instance().add("Hello to you too!");
          }
      }
      到这里你会发现我们根本没有用事件对象!实际上,事件产生者和事件消费者之间根本不必有事件对象来
      处理状态的问题。状态问题由Seam的上下文处理了,并且在Seam组件之间共享!
      不过,你要真的想要一个事件对象,可以象这样:
      @Name("helloWorld") // 事件产生者
      public class HelloWorld {
          private String name;
          public void sayHello() {
              FacesMessages.instance().add("Hello World, my name is #0.", name);
              Events.instance().raiseEvent("hello", name); // name是String参数,传递多个Object参数
          }
      }
      @Name("helloListener") // 事件监听者
      public class HelloListener {
          @Observer("hello")
          public void sayHelloBack(String name) {
              FacesMessages.instance().add("Hello #0!", name);
          }
      }
    9. Seam具有上下文的事件,Seam本身定义了一系列内置的事件。注意这些事件的名称都是字符串定义的:

              Seam组件可以监听(观察)这些事件,就像他们观察组件驱动的事件一样。我们将在别的文章中专门讲述。

    October 26

    IkeWiki: 协同知识管理的语义Wiki (3)

    4 IkeWiki
    A number of semantic wiki systems are currently under
    development.7 In the following, we introduce one such system
    called IkeWiki. Like “wiki”, the name is derived from
    Hawaiian and can be translated as “fast knowledge”.


    4.1 Design Principles
    Although now also considered in different settings,
    IkeWiki has originally been developed as a prototype tool
    to support knowledge workers in collaboratively formalising
    knowledge [4]. IkeWiki’s design principles are influenced
    by this idea, but equally hold in other areas:


    Easy to Use, Interactive Interface. As domain experts
    are usually non-technical people, ease of use is of big
    importance. Therefore, IkeWikis interface resembles as
    closely as possible theWikipedia interface which people are
    familiar with. Also, IkeWiki offers an interactive WYSIWYG
    editor (using AJAX technology to communicate with
    the server backend) in addition to the traditional structured
    text editor, as WYSIWYG editors generally have a better
    acceptance among non-technical users. The WYSIWYG
    editor also supports interactive typing of links and
    resources.
    At the same time, IkeWiki offers all the normal editing
    interfaces (supporting Wikipedia-style structured text) for
    more experienced users.


    Compatibility with Wikipedia/MediaWiki. A significant
    amount of “informal” knowledge is available in
    Wikipedia. To reuse this knowledge, IkeWiki supports the

    Wikipedia syntax. This allows users to import existing content
    from Wikipedia into IkeWiki (e.g. via simple copy and
    paste) and begin with semantic annotations straight away.

    能够和其他应用程序交换数据,例如本体编辑器,语义Web服务,其他Wiki等。
    Compatibility with Semantic Web standards. To be
    able to exchange data with other applications (e.g. ontology
    editors, Semantic Web services, other wikis), IkeWiki
    is purely based on existing Semantic Web standards like
    RDF and OWL. Note that other knowledge representation
    formats like conceptual graphs are conceivable but not investigated
    at this point.

    即刻开发和发布语义注释
    Immediate Exploitation of Semantic Annotations. An
    important motivating aspect of wiki systems is that content
    is immediately available to the public when a user clicks on
    “save”. Similarly, IkeWiki allows immediate exploitation
    of semantic annotations for enhanced editing, presentation,
    navigation, and searching, even if the knowledge base is not
    yet fully formalised.

    支持不同级别的经验
    Support for Different Levels of Experience. IkeWiki is
    designed as a tool for collaborative knowledge engineering.
    In such a process, it is common that non-technical
    domain experts work together with experienced knowledge
    engineers. Therefore, IkeWiki supports all levels of experience.
    This means that certain advanced functionalities can
    be hidden from novice users but are available to experienced
    users.

    支持不同级别的正式化
    Support for Different Levels of Formalisation. Different
    application areas need different levels of formalisation
    [4], and as Jim Hendler said:8 “a little semantics goes a long
    way”. One of the goals of IkeWiki is thus to support formalisation
    of knowledge all the way from informal texts to
    formal ontologies. Also, this means that parts of the knowledge
    base might be more formalised than others, and that
    formal knowledge is in constant evolution.

    支持推理,目前只支持OWL-RDFS推理
    Support for Reasoning. Unlike most other semantic
    wikis, IkeWiki supports reasoning on the knowledge base.
    We consider reasoning important as it allows to derive
    knowledge that is not explicit in the data and is thus the
    true power of Semantic Web technology. At the moment,
    IkeWiki supports only OWL-RDFS reasoning, but an extension
    with a user-accessible rule engine is planned.

    4.2 Architecture

     

    IkeWiki is implemented as a Java web application using
    a layered architecture shown in Figure 1. Data is stored in a

    Postgres database. When a resource is requested, the XML
    page content and related RDF data are retrieved and combined
    in the RenderingPipeline into an enriched XML representation.
    This XML representation is then either offered
    as interchange format for otherWeb services or transformed
    into HTML for presentation in the user’s browser. The individual
    layers are described in more detail in the following:

    页面内容的存储,支持版本和重建,使用WIF格式
    Page Store. The page store component serves to store and
    retrieve page content from the database. It also supports versioning
    and restoring of previous revisions. Page content
    is represented in structured text as well as an XML format
    we call WIF (wiki interchange format). Basic WIF merely
    describes the page content and structure, but allows to add
    custom, application specific information in separate namespaces.
    We have proposed WIF as a format for interchange
    of page content between arbitrary wiki systems.

    RDF存储,知识库通过Jena的RDF框架展示。IkeWiki使用内存模型来保证系统的响应度。内存模型常常和数据库模型同步。部分RDF存储是一个SPARQL引擎,允许查询知识库。


    RDF Store. The knowledge base is represented using the
    Jena RDF framework.9 IkeWiki uses an in-memory model
    to ensure responsiveness of the system. The in-memory
    model is frequently synchronised with a database model for
    persistent storage. Part of the RDF store is a SPARQL engine
    that allows for searching of the knowledge base.

    绘制管道将页面内容和语义注释结合起来,输出是WIF文档,包含相关的语义注释(例如连接类型,上下文适配),文档是RXR格式的。文档由很多Wiklet组成,每个Wiklet向WIF文档添加特定的信息片段。Wiklet可以根据权限来设置用户是否可以访问,从而控制不同用户的视图。


    Rendering Pipeline. The Rendering Pipeline combines
    page content with semantic annotations. Its output is a WIF
    document enriched by relevant semantic annotations (e.g.
    link types, context adaptation) in RXR format [2]. The
    “pipeline” consists of small steps we call “wiklets”. Each
    wiklet adds a specific piece of information to the WIF document.
    Wiklets can be enabled and disabled and associated
    with permissions so that only selected users can see
    the added information.

     

    Transformation, Servlet. The transformation layer applies
    XSLT transformations to the enriched WIF format to
    generate XHTML, SVG, and/or WIF output. The IkeWiki
    servlet then serves this output to the user’s browser.

    4.3 Interface 界面
    IkeWiki uses a purely browser-based interface (cf. Figure
    2). The current implementation only supports the
    Mozilla browser family due to its standards compliance and
    free availability.

    例子:生物学越桔(Bilberry)的页面。

    Page View. A sample page view is shown in Figure 2.
    In the figure, you can see a sample article (copied from
    Wikipedia) about the “Bilberry”.

    页面的类型信息在标题下方:Types:  skoes:Concept - rdfs:Resource

    Type information is shown below the page title (1).

    语义相关的页面在页面右侧单独的引用框(Reference Box)内显示。

     Links to (semantically) related pages are displayed in a separate “references box” on
    the right hand side (2).

    分类框显示生物学分类:越桔的科学分类,纲:双子叶(Magnoliopsida), 科:杜鹃花科(Ericaceae),属: 越桔属(Vaccinium)。注意:这是根据知识库中已经存在的语义注释自动生成的(例如,越桔 具有属 越桔属),这个分类显示了上下文适配功能。

    The taxonomy box (3) showing the biological classification of the described plant is automatically
    generated from existing semantic annotations (i.e. Bilberry
    hasGenus Vaccinium) and is an example for context
    adaptation.

    最后我们可以用AJAX技术表现交互的连接注释功能。

    Finally, (4) shows interactive typing of links using
    AJAX technology.

    内容编辑器有两种选择:传统的Wiki文本输入和所见即所得的编辑器
    Content Editor. The content editor is available in two
    flavours: as a traditional structured text editor and as a
    WYSIWYG editor. The structured text editor is aimed at
    expert users that are familiar with other wiki systems, and
    allows to directly copy content fromWikipedia. The WYSIWYG
    editor is aimed at novice users creating new content.
    The WYSIWYG editor interacts with the server backend:
    links are automatically recognised and verified, and semantic
    annotations can be done directly in the editor (as also
    shown in Figure 2, (4)).

    语义注释编辑器。语义注释分为三个编辑器:元数据编辑器允许输入和这个页面相关的文本元数据(就像Dublin Core元数据或者RDF Comment类似)。类型编辑器允许将系统中一个或者多个类型和这个页面相关联。连接编辑器允许通过类型信息注释出去(outgoing)和进入(incoming)的连接。在编辑器中,可以获得的注释是由基于页面和连接类型的推理器决定的。例如,一个从莫扎特页面到安魂曲页面的连接被注释为”是...的创作者“,系统自动将”创作者“这个类型和莫扎特页面相关联。用户无法直接删除这个类型。
    Semantic Annotations Editor. Semantic annotations are
    separated into three editors: the metadata editor allows to
    fill in textual metadata related to a page (like Dublin Core
    metadata or RDF comments). The type editor allows to associate
    one or more of the types available in the system with
    a page. The link editor allows to annotate outgoing and incoming
    links with type information. In the editors, available
    annotations are determined by the reasoner based on the
    page and link types; for example, if a link from “Mozart”
    to “Die Zauberfl¨ote” is annotated by “composerOf”, the
    system will automatically associate the type “Composer”
    with the page describing “Mozart”, and this type cannot be
    deleted directly by the user.

    image

    IkeWiki: 协同知识管理的语义Wiki (4)

    语义Wiki使用的场景

    5 Semantic Wiki Usage Scenarios
    A wide range of application scenarios for semantic wikis
    are conceivable. In the following, we briefly present three
    areas that might benefit from semantic wikis,

    ontology engineering, 本体工程
    knowledge management, 知识管理

    and educational environments. 教育环境

    在本体工程中,领域专家和知识工程师一起工作,创建正式的本体论。语义Wiki能够很好的支持这个过程

    本体工程
    Ontology Engineering. In ontology engineering, domain
    experts and knowledge engineers work together to create a
    formal ontology. Semantic wikis can support this process
    very well:
    • non-technical domain experts are offered an easy way to enter their knowledge;非技术的领域专家能够容易的输入他们的知识
    • domain experts and knowledge engineers can collaboratively work on the ontology, each bringing their personal expertise; and 领域专家可以和知识工程师在本体论构建上协同工作,每个人都带来自己的专业

    • the knowledge can be formalised in an evolutionary process, beginning with informal texts. 知识可以在不断演变的过程中从不正式的文本变为正式化

    可以考虑一个历史学家想要创建一个关于莫扎特的语义门户。他开始就通过编辑器向语义Wiki中添加文本,图片,甚至语音内容,他也可以在内容间描述一些简单的关系。同时,他的语义网技术不太足够了,于是他叫来一个知识工程师。知识工程师不太清楚主题,可以先弄一些简单的关系,然后将它们正式化为一个本体,同时,这个历史学家可以继续向系统添加更多的内容。
    As an example, consider a historian who wants to create a
    semantic portal about Mozart. He begins filling the semantic
    wiki with texts, images, and maybe even audio content
    he has available via an easy to use editor. He can also describe
    simple relations between content. At some point, his
    experience in Semantic Web technologies does no longer
    suffice, so he brings in a knowledge engineer. The knowledge
    engineer – without in-depth knowledge of the topic
    – can take the simple relations and further formalise them
    e.g. to form an ontology. At the same time, the historian can
    continue filling the system with more content.

    知识管理
    Knowledge Management. In our knowledge-based
    economy, knowledge management is becoming more and
    more important. Knowledge management means easy and
    free authoring and content creation on the one hand (as one
    did on sticky notes, etc), and structuring knowledge for
    easy retrieval and for finding related information one was
    not aware of on the other hand. Also, sharing of knowledge
    is an important issue. While traditional wikis already support
    much of this, semantic wikis offer advanced searching
    and navigation capabilities as described above. They are
    therefore very well suited for knowledge management.
    Note that [6] describes in detail the application of semantic
    wikis for knowledge management.

    教育环境
    Educational Environments. In educational environments,
    semantic wikis can serve many different purposes.
    This paragraph focusses on two of them, content creation
    and learning.
    Content Creation. In today’s knowledge society,
    learning content needs to quickly adapt to the specific needs
    of learners (“just in time learning”). Creation of appropriate
    content is, however, usually a time consuming task. A
    semantic wiki can be very helpful for content creation. The
    wiki represents an easily modifiable “learning content pool”
    where content creators can author, search for, and recombine
    learning material to form learning content tailored towards
    the learner’s needs. Semantic annotations can be used
    to facilitate retrieval of related content, to “match” learning
    material in a course, and to create the structure for a new
    course, which can then be exported e.g. to SCORM.
    Learning. In constructivist and connectivist learning
    theories, users learn by constructing knowledge in their
    minds based on their experiences. Also, learning – especially
    of adults – is nowadays often at least partly selfdirected.
    Semantic wikis are a good tool to support such
    learning, as they allow learners to collect, connect, and
    structure information that they consider relevant, and to
    collaboratively collect and share information in a learning
    group. Also, they allow easy supervision/support by
    a coach.


    6 Related Work
    In the last year, a lot of semantic wiki systems with
    sometimes very different purposes and functionalities have
    been proposed, e.g. [6, 3, 1, 5]. The first such system was
    probably PlatypusWiki [5], which is focussed on the creation
    of RDF data. A constantly updated list of systems can
    be found at the SemanticWiki Interest Group website.10 As
    a response to the growing interest, the SemanticWiki workshop
    (SemWiki06), co-located with ESWC’06 in Budva,
    Montenegro, will take place for the first time.


    7 Perspectives and Conclusion
    In this article, we presented IkeWiki, a feature-rich semantic
    wiki system. Future directions for the development
    of IkeWiki might be to provide more support to knowledge
    engineers through linguistic processing techniques, identification
    of similar concepts through automated structure analysis,
    and guided questions based on underlying ontological
    knowledge. Also, it is intended to include a rule engine that
    allows users to define custom rules for the knowledge base.
    On the application side, we are currently working on several
    scenarios for the use of semantic wikis in elearning
    environments, and we are investigating the use of IkeWiki
    as part of a methodology for collaborative knowledge engineering
    developed in the project Dynamont.


    References
    [1] D. Aumueller and S. Auer. Towards a Semantic Wiki Experience
    – Desktop Integration and Interactivity in WikSAR. In
    Semantic Desktop Workshop 2005 at ISWC’05, Galway, Ireland,
    2005.
    [2] D. Beckett. Modernising Semantic Web Markup. In XML
    Europe 2004, Amsterdam, The Netherlands, 2004.
    [3] M. Kr¨otsch, D. Vrandeˇci´c, and M. V¨olkel. Wikipedia and the
    Semantic Web - The Missing Links. In Proceedings of the
    WikiMania2005, 2005.
    [4] S. Schaffert, A. Gruber, and R. Westenthaler. A Semantic
    Wiki for Collaborative Knowledge Formation. In Semantics
    2005, Vienna, Austria, November 2005.
    [5] R. Tazzoli, P. Castagna, and S. E. Campanini. Towards a
    Semantic WikiWikiWeb. In 3rd International Semantic Web
    Conference (ISWC2004), Hiroshima, Japan, 2004.
    [6] M. V¨olkel and E. Oren. Personal Knowledge Management
    with Semantic Wikis. 2006.

    IkeWiki: 协同知识管理的语义Wiki (2)

    3 Semantic Wiki Systems

    RDF,OWL,主题图,概念图...

    连接,连接!不仅仅是导向,更重要的是对连接的注释,通过对连接的注释,表达了页面间的逻辑含义,这可以让机器能够处理!例如描述莫扎特的网页和萨尔茨堡的网页间的注释应该是“生于”: 莫扎特 生于 萨尔茨堡! 
    A “semantic wiki” extends a wiki by “semantic technologies”
    like RDF, OWL, Topic Map, or Conceptual Graphs.
    The main idea is to make the inherent structure of a wiki –
    given by the strong linking between pages – accessible to
    machines (agents, services) beyond mere navigation. This
    is generally done by annotating existing navigational links
    with symbols that describe their meaning. For example, a
    link from Mozart to Salzburg could be annotated with lived
    in or born in.

    注释能够增强上下文信息的展示,能够增强相关页面间的导航,根据内容的上下文增强语义搜索。展示,导航,搜索可以以更自然的方式完成。

    Such annotations are useful for many purposes, e.g. enhanced
    presentation by displaying contextual information,
    enhanced navigation by giving easy access to relevant related
    information, and enhanced “semantic” search that respects
    the context in addition to the content. Note that presentation,
    navigation, and search can be done in a rather
    generic manner, but often profit greatly from an adaptation
    to the represented context.

    有些时候页面间的注释比页面本身内容还要重要!
    Semantic wikis exist in many different flavours (e.g. Se-
    MediaWiki [3], SemWiki [6], IkeWiki [4], PlatypusWiki
    [5]). While for some the page content is still in the foreground
    and the annotations just optional “added value”, others
    require annotations – sometimes to the extent where the
    annotation is more important than the page content itself.
    This reflects also that different systems have different purposes,
    e.g. extending existing content by annotations to allow
    for better navigation, collaborative ontology engineering,
    etc. Nonetheless, frequently found features are:

    所有的语义Wiki通过赋给连接特定的类型来注释连接。背后的含义是如果用户在一个页面中创建了一个指向另一个页面的连接,那么他一定想表达某种意思,或者某种关联,而不仅仅是简单的连接,想想莫扎特的例子!不同的系统中连接的注释方式不同。一些语义Wiki将注释作为Wiki语法的一部分(例如语义MediaWiki),而另外的系统将提供一个单独的编辑器来增加注释(例如IkeWiki)

    类型化或注释连接
    Typing/Annotating of Links. Virtually all semantic
    wikis allow to annotate links by giving them certain types.
    The idea behind this is that a link created by a user almost
    always carries meaning beyond mere navigation, as given in
    the example in the beginning of this section. The way link
    annotations are edited differs from system to system. Some
    semantic wikis include the annotations as part of the wiki
    syntax (e.g. Semantic MediaWiki [3]), while others provide
    a separate editor for adding annotations (e.g. IkeWiki).

    很多语义Wiki能够基于语义注释来改变内容的展示方法。可以在页面的某个地方显示语义相关的页面,这些相关页面的信息来源于底层的知识库(比如一个树图显示页面内容属于某个层次结构)

    具有上下文意识的展示
    Context-Aware Presentation. Many semantic wikis can
    change the way content is presented based on semantic annotations.
    This can include enriching pages by displaying
    of semantically related pages in a separate link box, displaying
    of information that can be derived from the underlying
    knowledge base (e.g. a box with a graphical tree presentation
    for content belonging to a hierarchy, or license information),
    or even rendering the content of a page in a different
    manner that is more suitable for the context (e.g. multimedia
    content vs. text content).

    传统的Wiki仅仅允许跟随一个连接(点击打开页面),语义Wiki能够做的更多,例如文本上下文出现的连接更为独立,能够在单独的相关信息栏中显示。例如莫扎特页面可以提供一个独立的信息栏显示居住于,作品等分类引用等。

    增强的导航
    Enhanced Navigation. Annotated/typed links provide
    more information for navigation. Whereas a traditional wiki
    only allows to follow a link, a semantic wiki offers additional
    information about the relation the link describes.
    Such information can be used to offer additional or more
    sophisticated navigation. For instance, links are more independent
    from the textual context they appear in and can be
    displayed e.g. in a separate “related information” box. The
    page describing Mozart could e.g. offer a separate box with
    references categorised by “lived in”, “composed”, etc.

    大部分语义Wiki能够通过SPARQL语言,提供基于底层知识库的语义搜索,这种语言用来进行RDF的查询。通过语义查询,用户能够查询”莫扎特创作的所有曲子“或”查找所有和版本许可相关工作的文档“。

    语义搜索

    Semantic Search. Most semantic wikis allow a “semantic
    search” on the underlying knowledge base. Usually, queries
    are expressed in the language SPARQL, a query language
    recently proposed as W3C recommendation for RDF querying.
    Using “semantic search”, users can ask queries like
    “retrieve all pieces composed by Mozart” or “retrieve all
    documents where the license permits derivative works”.

    推理意味着使用预定义或者用户定义的知识库规则从系统中保存的事实衍生出附加的隐含的知识。例如,系统能够从莫扎特创作了安魂曲的事实能够推理出莫扎特是个作曲家。虽然推理是个很重要的特性,但是仅仅被一小部分Wiki支持。原因有多个方面:耗时,耗内存,产生不期望的结果,或者用户无法跟踪的结果。

    推理支持
    Reasoning Support. Reasoning means deriving additional,
    implicit knowledge from the facts entered into the
    system using predefined or user-defined rules in the knowledge
    base. For example, from the fact that “Mozart” composed
    “Die Zauberfl¨ote”, a system capable of reasoning
    could deduce that “Mozart” is a “Composer”. Although
    reasoning is an important feature, it is only supported by
    a small number of Wikis. The reasons for this might be
    that it is time-consuming, memory intensive, and can yield
    results that are not expected and/or traceable by the user.