alartin's profileWindows Live 共享空间PhotosBlogListsMore ![]() | Help |
|
January 22 Facelet: JSF的最佳拍档阅读这篇文章之前,我们先回顾一下,为什么我们需要JSF。很显然,我们不满意基于页面这种杂乱的Web视图设计体系。JSF就是提供一种基于组件,具有事件模型和组件模型的方式让我们象开发Swing图形界面一样开发Web程序。很美妙?不是么? 那么我们为什么还需要Facelet呢?我想这一定是大家最关心的问题。 文章列表: 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种:
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支持多种变量值的类型:
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属性的各种值来控制这一点:
任务实例也可以通过运行时计算而创建。在这种情况下,在任务节点的进入节点事件中添加一个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. 任务指派的数据模型 任务实例或泳道实例的参与者ID actorId负责这个任务。而其中的PooledActors, 参与者候选池维护一系列任务的候选者,每个候选者都有可能负责这个任务,当候选者获得这个任务 后,他就为这个任务负责。需要注意的是:参与者ID和参与者候选池是可选的,也可以组合在一起使用。 个人的任务列表 个人任务列表负责维护指派给特定个人的任务,通过任务实例的参与者ID就可以看出。所以要想将任务实例放入个人任务列表中,只需下列方法的一种:
我们可以通过TaskMgmtSession.findTaskInstances(String actorId)来获取特定人的任务列表。 组的任务列表 参与者候选池指的是任务实例的候选者。这意味着这个任务可以提供给多个用户,其中一个候选者可以被设置接受这个任务。候选池中的参与者可以看作是同一个组的,如果这个任务是提供给候选池的,用户必须先将任务拿到个人的任务列表中,才能执行任务。 TODO 背后的动机是我们希望将Identity组件从JBPM的任务指派中剥离。JBPM应该只关注actorId这些String类型的对象,而不应该关注用户,组或者其他识别信息之间的关系。 actorId总是覆盖参与者候选池中的actor(pooled actor)。所以如果任务实例既有actorId,也有参与者候选池的话,那么这个任务实例只出现在个人任务列表中。不过,参与者候选池有重要作用,一个用户可以将任务实例放回到组中重新供参与者候选人获取,这些只需要将任务实例的actorId属性去掉即可。 任务实例变量 任务实例可以拥有自己的一套变量,并且任务实例能够“看见”流程变量。任务实例是在一条执行路径token中创建的。需要注意的是:token和在这个token中创建的任务实例之间也是父子关系,这和token和其子token是父子关系是一样的。任务实例的变量和token相关的流程变量之间的范围规则同样适用。这意味着一个任务实例能够看到自己的一套变量,加上和它相关的token的所有变量。 任务控制器能够用来在任务实例范围和流程范围变量之间传递和提交变量。 任务控制器 任务控制器本质上是流程变量和用户界面程序的桥梁。从这张图片可以看出,任务是以任务实例的方式赋给特定的用户的,任务控制器任务实例负责流程变量和任务变量之间的传递。用户只能通过任务实例中的变量和流程打交道,因此用户界面接口中的输入输出都是流程变量<->任务控制器<->任务实例<->用户界面。因此实际上用户在界面中输入的都是以任务变量的形式保存,当任务结束后通过任务控制器来将任务变量转化为流程变量。 最简单的情况下,流程变量和用户的表单参数是一对一的。任务控制器在任务元素中指定。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页面导航配置的三要素:
但是你可能立刻就能发现这种处理方式的弊病来:
这种方式的页面导航被称为无状态的页面导航。 需要强调的是:对于这种无状态的页面导航方式,除了JSF导航(使用navigation.xml)外,Seam还支持一种自己实现的无状态页面导航(使用pages.xml或者某个页面的xxx.page.xml): <page view-id="/admin/admin.xhtml"> 这里Seam也使用了类似JSF的页面导航三要素:
如果,在你的Seam应用程序中你准备使用无状态的页面导航的话,你有两种选择:
我们将在后面的文章中比较这两种选择。 Seam不仅仅只支持这种无状态的页面导航!更棒的是,Seam支持有状态的页面导航! 提醒:在Seam中,Seam实现的页面导航被成为页面流Pageflow。
在我们讲解有状态的页面导航之前,我们先回顾一下无状态页面导航的弊病包括:虽然页面配置简单,但是代码中要控制页面导航规则;无法很容易的看清楚导航规则,因为约束小。 Seam通过使用JBPM来帮助我们支持创建有状态的页面导航! 需要注意的是:Seam在两种层次上使用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)来配置的,几个要素:
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 ExecutionJBPM中,图的执行模型是基于对流程定义的解释和命令模式链的。 对流程定义的解释意味这流程定义必须存储在数据库中(其实不尽然,但必须有某种存储机制)。在流程执行过程中需要流程定义的信息。需要注意的是JBPM使用hibernate的二级缓存来避免在运行时加载流程定义信息。这是因为一般来说流程定义不会轻易改变,所以我们能够将流程定义放入缓存中。 命令模式链意味着图中的每个节点需要负责将流程的执行传递下去。如果某个节点没有传递流程的执行,那么这个节点位于等待状态。 JBPM的设计思想是在流程实例上启动执行,然后这个流程执行将一直继续下去直到进入一个等待状态。 一个令牌token代表一个执行的路径。一个token拥有一个指向流程执行中的一个节点的指针。同样token也可以通过数据库将自己持久化。在等待状态时,token可以被持久化到数据库中。我们看一下JBPM是如何计算token的执行的。当有一个信号singal发送给token的时候,执行开始启动,接着执行通过命令模式链的方式沿着转移transition和节点node传递。在类定义中有相关的方法: 当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)来负责传递流程的执行。 简单的说:
需要注意的是整个的下一状态的计算(包括动作的调用)都是在客户端线程中完成的。一个常见的错误理解是所有的计算都必须在客户端线程中完成。对于任何的异步调用,你可以使用异步消息JMS。当消息在流程实例更新的同一个事务中被发送时,所有的同步问题都要小心的处理。一些工作流系统在图中的所有节点间使用的是异步消息。但是在高通量环境中,这种算法能够为业务流程提供更高的灵活性和控制性。 December 20 JBPM术语:动作Action概述 动作是在流程执行中的事件中的Java代码片段。你可以认为动作是可以在事件上发生的(注意,也可以在节点内发生)。为何要有动作这个概念呢?原因在于图虽然是软件需求间通讯的重要设备,但是图只是软件的一个视图或者投影,它隐藏了太多技术细节(流程图主要给业务分析员看或者编辑的),而动作是给流程图增加技术细节的机制(开发者需要用到它)。我们需要动作来装点图,这样的图才有意义。 因为动作多少和事件有关系,我们看一下事件类型。主要的事件类型包括:进入节点,离开节点,走向转移。 注意:动作可以在事件中发生,也可以在节点中发生!而这两种情况非常不同!
我们看一下事件中动作的例子,假定我们需要在一个指定的转移中更新数据库,更新数据库是技术细节,业务分析员不关心这个。这个例子是如果开除一个员工,我们需要将他的工作证收回,在这个转移中,需要更新人力资源的数据库。 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已经具有多个预定义的节点类型。当然你可以定制自己的节点从而实现特殊需求的节点行为。那么节点负责什么呢? 节点主要负责两件事情:
传递流程的执行 每个节点都有以下几种方式传递流程的执行:
节点的类型 JBPM包含了很多预定义和实现的节点类型,这包括:
JBPM术语:图的元素GraphElement概念 GraphElement是JBPM的一个定义,位于org.jbpm.graph.def包中。GrahpElement是个抽象类。 GraphElement有一个名字name, 一个描述description. GraphElement有多个事件event,使用Map来存储。 GraphElement有多个例外处理器exceptionhandler, 使用List来存储。 GraphElement有一个流程定义processDefinition. 有意思的是:processDefinition本身就是GraphElement的子类。 子类
重要的方法
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并不传递执行,说明这个节点是一个状态。 重要的方法
token.signal()方法能够激活token本身,然后让token离开当前状态,走向默认的转移transition 带参数的signal()方法让token走向指定的转移transition
token.suspend()方法能够将token暂停挂起
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输入的值绑定一样。
关键的思想是我们如何从另一个页面到/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页面时,页面参数的传递就有必要了。你可以通过页面参数的传递来记住先前你在哪个视图上 (例如你在保存页面上,必须记住上个页面你正在编辑的实体)
听起来太复杂了,这有必要么?当你用它的时候你就知道了。这值得下功夫去理解。页面参数是在非Faces请求间传递状态的最佳方式。如果你想让你的搜索结果页面也能做书签的话,我们就能够在同一个代码中使用它来处理POST和GET两种请求了。页面参数能够减少视图定义中请求参数列表的重复性,并且让在代码中处理重定向更加简单。 对话和验证 导向 在Seam中,你可以使用JSF的faces-config.xml文件来设置标准的JSF向导规则。但是JSF的向导规则有很多缺点:
还有一个问题就是如果在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提供了内置的上下文事件,我们可以为每个事件定制自己的监听器: 关于验证的事件
关于变量的事件
关于上下文的事件
关于对话的事件
关于页面流的事件
关于流程和任务的事件
关于组件的事件
关于JSF生命周期的事件
关于认证和安全的事件
关于例外的事件
关于事务的事件
JBoss Seam的事件机制(1):概述JBoss Seam如何做到松耦合的架构呢?光有具备上下文的组件模型是不够的。还必须有:
JBoss Seam的组件模型本身就是为事件驱动的应用来设计的。这些事件都是通过JSF的表达式语言的方法绑定来映射的。在JBoss Seam中,事件可以分为:
<h:commandButton value="Click me!" action="#{helloWorld.sayHello}"/> JSF的按钮绑定动作
<start-page name="hello" view-id="/hello.jsp">
<transition to="hello">
<action expression="#{helloWorld.sayHello}"/>
</transition>
</start-page> jBPM流程或页面流定义
<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><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);
}
}
Seam组件可以监听(观察)这些事件,就像他们观察组件驱动的事件一样。我们将在别的文章中专门讲述。 October 26 IkeWiki: 协同知识管理的语义Wiki (3)4 IkeWiki
Wikipedia syntax. This allows users to import existing content 能够和其他应用程序交换数据,例如本体编辑器,语义Web服务,其他Wiki等。 即刻开发和发布语义注释 支持不同级别的经验 支持不同级别的正式化 支持推理,目前只支持OWL-RDFS推理 4.2 Architecture
IkeWiki is implemented as a Java web application using Postgres database. When a resource is requested, the XML 页面内容的存储,支持版本和重建,使用WIF格式 RDF存储,知识库通过Jena的RDF框架展示。IkeWiki使用内存模型来保证系统的响应度。内存模型常常和数据库模型同步。部分RDF存储是一个SPARQL引擎,允许查询知识库。
绘制管道将页面内容和语义注释结合起来,输出是WIF文档,包含相关的语义注释(例如连接类型,上下文适配),文档是RXR格式的。文档由很多Wiklet组成,每个Wiklet向WIF文档添加特定的信息片段。Wiklet可以根据权限来设置用户是否可以访问,从而控制不同用户的视图。
Transformation, Servlet. The transformation layer applies 4.3 Interface 界面 例子:生物学越桔(Bilberry)的页面。 Page View. A sample page view is shown in Figure 2. 页面的类型信息在标题下方: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 分类框显示生物学分类:越桔的科学分类,纲:双子叶(Magnoliopsida), 科:杜鹃花科(Ericaceae),属: 越桔属(Vaccinium)。注意:这是根据知识库中已经存在的语义注释自动生成的(例如,越桔 具有属 越桔属),这个分类显示了上下文适配功能。 The taxonomy box (3) showing the biological classification of the described plant is automatically 最后我们可以用AJAX技术表现交互的连接注释功能。 Finally, (4) shows interactive typing of links using 内容编辑器有两种选择:传统的Wiki文本输入和所见即所得的编辑器 语义注释编辑器。语义注释分为三个编辑器:元数据编辑器允许输入和这个页面相关的文本元数据(就像Dublin Core元数据或者RDF Comment类似)。类型编辑器允许将系统中一个或者多个类型和这个页面相关联。连接编辑器允许通过类型信息注释出去(outgoing)和进入(incoming)的连接。在编辑器中,可以获得的注释是由基于页面和连接类型的推理器决定的。例如,一个从莫扎特页面到安魂曲页面的连接被注释为”是...的创作者“,系统自动将”创作者“这个类型和莫扎特页面相关联。用户无法直接删除这个类型。 IkeWiki: 协同知识管理的语义Wiki (4)语义Wiki使用的场景 5 Semantic Wiki Usage Scenarios ontology engineering, 本体工程 and educational environments. 教育环境 在本体工程中,领域专家和知识工程师一起工作,创建正式的本体论。语义Wiki能够很好的支持这个过程 本体工程 • the knowledge can be formalised in an evolutionary process, beginning with informal texts. 知识可以在不断演变的过程中从不正式的文本变为正式化 可以考虑一个历史学家想要创建一个关于莫扎特的语义门户。他开始就通过编辑器向语义Wiki中添加文本,图片,甚至语音内容,他也可以在内容间描述一些简单的关系。同时,他的语义网技术不太足够了,于是他叫来一个知识工程师。知识工程师不太清楚主题,可以先弄一些简单的关系,然后将它们正式化为一个本体,同时,这个历史学家可以继续向系统添加更多的内容。 知识管理 教育环境
IkeWiki: 协同知识管理的语义Wiki (2)3 Semantic Wiki Systems RDF,OWL,主题图,概念图... 连接,连接!不仅仅是导向,更重要的是对连接的注释,通过对连接的注释,表达了页面间的逻辑含义,这可以让机器能够处理!例如描述莫扎特的网页和萨尔茨堡的网页间的注释应该是“生于”: 莫扎特 生于 萨尔茨堡! 注释能够增强上下文信息的展示,能够增强相关页面间的导航,根据内容的上下文增强语义搜索。展示,导航,搜索可以以更自然的方式完成。 Such annotations are useful for many purposes, e.g. enhanced 有些时候页面间的注释比页面本身内容还要重要! 所有的语义Wiki通过赋给连接特定的类型来注释连接。背后的含义是如果用户在一个页面中创建了一个指向另一个页面的连接,那么他一定想表达某种意思,或者某种关联,而不仅仅是简单的连接,想想莫扎特的例子!不同的系统中连接的注释方式不同。一些语义Wiki将注释作为Wiki语法的一部分(例如语义MediaWiki),而另外的系统将提供一个单独的编辑器来增加注释(例如IkeWiki) 类型化或注释连接 很多语义Wiki能够基于语义注释来改变内容的展示方法。可以在页面的某个地方显示语义相关的页面,这些相关页面的信息来源于底层的知识库(比如一个树图显示页面内容属于某个层次结构) 具有上下文意识的展示 传统的Wiki仅仅允许跟随一个连接(点击打开页面),语义Wiki能够做的更多,例如文本上下文出现的连接更为独立,能够在单独的相关信息栏中显示。例如莫扎特页面可以提供一个独立的信息栏显示居住于,作品等分类引用等。 增强的导航 大部分语义Wiki能够通过SPARQL语言,提供基于底层知识库的语义搜索,这种语言用来进行RDF的查询。通过语义查询,用户能够查询”莫扎特创作的所有曲子“或”查找所有和版本许可相关工作的文档“。 语义搜索 Semantic Search. Most semantic wikis allow a “semantic 推理意味着使用预定义或者用户定义的知识库规则从系统中保存的事实衍生出附加的隐含的知识。例如,系统能够从莫扎特创作了安魂曲的事实能够推理出莫扎特是个作曲家。虽然推理是个很重要的特性,但是仅仅被一小部分Wiki支持。原因有多个方面:耗时,耗内存,产生不期望的结果,或者用户无法跟踪的结果。 推理支持 |
|
|