写一个简单的工作流(四)资源的处理

昨天晚上搞到深夜,终于将资源模块搞定。到今天已经完成的功能包括:
1.四种基本路由:顺序、选择、并行、循环
2.流程定义文件和系统配置文件的读取和解析
3.使用内存作为流程数据和案例数据存储的MemoryWorkFlowDAO的开发
4.资源模块的开发
5.并发情况下的正确性测试等

    计划中的功能:
1.一个GUI的流程定义工具,这个不急,也还没想好用什么做,web还是桌面?
2.各个数据库版本的WorkFlowDAO的开发,将流程数据和案例数据保存在数据库中。
3.更多的测试和example试验。

    回到资源这个概念,工作流中工作项(work item)的由资源来驱动的,这个资源(resource)可能是用户、角色、定时时间或者某个事件消息。在标准petri网中,工作项对应于transition(变迁),变迁都是自动的,不需要所谓资源来驱动,显然,这与工作流系统不同。具体到insect workflow(我取的名字,小巧之意),每个transition都有一个resource,用于驱动自身的firing,所有的resource都实现Resource接口:

public interface Resource extends Serializable {

    public void start(Transition transition, Token token, Object args);
           
    public ResourceType getType();

    public long getId();

}

    每个资源都有一个类型,以及这个类型中独一无二的id,start方法用于驱动transtion的firing。一般情况下,你不需要实现这个接口,只要继承这个接口的抽象实现类AbstractResource,AbstractResource的start方法默认实现是首先调用模板方法doAction(稍后解释),然后检查触发条件,如果通过就直接调用transition的fire方法:

public abstract class AbstractResource implements Resource {
         
    public void start(Transition transition, Token token, Object args) {
        doAction(transition, token, args);

        if (transition.getCondition() != null
                && !transition.getCondition().check(token))
            throw new ConditionException(transition.getName()
                    + " transition没有满足触发条件");
        transition.fire(token, args);
    }
    public abstract void doAction(Transition transition, Token token,
            Object args) ;
       
}

    Transtion类的fire方法有三个操作组成:从输入库所移走token,往输出库所放入token,回调handler:

    public void fire(Token token, Object args) {
        removeTokenFromInputs(token);
        addTokenToOutputs(token);
        invokeHandler(token, args);
    }

    那么具体的资源显然要实现AbstractResource中的doAction抽象方法,系统内置了五种资源:自动资源(AutoResource)、用户(User)、用户组(Group)、定时器(TimerResource)和事件监听器(ObserverResource)。显然,AutoResource、User和Group的doAction方法不需要做任何事情:

public class User extends AbstractResource {
    protected Group group;
            
    @Override
    public void doAction(Transition transition, Token token, Object arg){
    }

}

   
    而TimerResource就需要做特殊处理了,比如我们要达到这样的效果:节点1状态已经处于就绪,可以被触发,可我们希望在就绪后延迟半分钟再触发,或者在晚上10点触发等等。这样的定时需求很常见,我采用了jdk5引入的ScheduledExecutorService来处理。系统中启动这样一个线程池,每个类似上面的请求都提交给这个线程池来处理,那么TimerResource就需要进行相应的修改:

public abstract class TimerResource extends AbstractResource {

    protected int pool_size;

    protected static ScheduledExecutorService scheduledExecutorService;

    @Override
    public long getId() {
        // TODO Auto-generated method stub
        return Common.TIMER_RESOURCE_ID;
    }

    public TimerResource() {
        this.pool_size = 5;
        scheduledExecutorService = Executors.newScheduledThreadPool(pool_size);
    }

    public static void shutdownPool() {
        if (scheduledExecutorService != null)
            scheduledExecutorService.shutdown();
    }

    public final void start(Transition transition, Token token, Object args)
            throws InterruptedException {
        if (transition.getCondition() != null
                && !transition.getCondition().check(token))
            throw new ConditionException(transition.getName()
                    + " transition没有满足触发条件");
        transition.removeTokenFromInputs(token);
        doAction(transition, token, args);
    }

    protected class ChangeRunner implements Runnable {
        private Transition transition;

        private Token token;

        private Object[] args;

        public ChangeRunner(Transition transition, Token token, Object args) {
            this.transition = transition;
            this.token = token;
            this.args = args;
        }

        public void run() {
            if (transition.getCondition() != null
                    && !transition.getCondition().check(token))
                throw new ConditionException(transition.getName()
                        + " transition没有满足触发条件");
            transition.addTokenToOutputs(token);
            Object real_args[] = new Object[args.length - 2];
            for (int i = 0; i < real_args.length; i++)
                real_args[i] = args[i + 2];
            transition.invokeHandler(token, real_args);
            try {
                // 回调
                ((WorkFlowAlgorithm) args[1]).enabledTraversing(token
                        .getWorkFlow());
                ((WorkFlowManager) args[0]).doAction(token.getId());

            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
            }
        }
    }
}

    注意到,start方法不再是直接调用transition的fire方法,而仅仅是进行了第一步操作:移除输入库所的place防止重复提交。后两步操作都延迟到了提交给线程池的任务中,也就是代码中的ChangeRunner类中的run方法。例如TimerResource的子类DelayTimerResource用于处理延迟的触发,doAction就像这样:

public class DelayTimerResource extends TimerResource {
    
    @Override
    public void doAction(Transition transition, Token token, Object args){
        scheduledExecutorService.schedule(new ChangeRunner(transition, token,
                args), this.delay, this.timeUnit);

    }
}

    延迟的时间,时间单位这些信息都可以在流程定义文件中设置。事件监听器资源与此类似,ObserverResource实现了java.util.Observer接口,往输出库所放入token和回调handler两步操作被放在了update方法中提供给Subject回调。

文章转自庄周梦蝶  ,原文发布时间2007-10-13 
   

时间: 2024-09-14 04:53:35

写一个简单的工作流(四)资源的处理的相关文章

写一个简单的工作流,基于petri网

写一个简单的工作流一直停留在我的"计划"中,最近趁改造绩效系统的机会,决定自己写一个基于petri网原理的工作流来改写绩效考核流程部分.基于petri网的工作流的基本算法,就是当每一个firing发生后,应当遍历整个流程重新改变transition的enable,那么当资源驱动某个transition其实就是将它的输入place中的token转移到输出place.大概的接口类似: WorkFlowManager wm = new BasicWorkflowManager(this.wo

写一个简单的工作流(二)

    hoho,今天完成了选择路由的实现,完成了配置文件的读写和解析,流程定义文件还是决定采用xml文件,不过与其他工作流引擎采用的xml完全不同,因为是基于petri网的,因此引入了place的概念,比如下面这个4个节点的顺序路由的流程: <workflow maxCases="100">     <node type="start" name="start" id="0">         <

写一个简单的工作流(三)

上午测试了下并发情况下的表现,测试场景:一个有20个节点,包括选择.顺序.并行路由的流程,所有节点都设置为自动执行,1000个线程并发启动案例,也就是这个流程同时有1000个案例在跑,全部跑完结果差强人意,比单线程慢了接近30倍.仔细调整了算法和加锁粒度,尽管整体性能有所提高,但是多线程和单线程间执行的差距仍然没有多大变化,性能瓶颈还是在核心的调度算法上,还需要分析下.测试程序如下: package net.rubyeye.insect.workflow.test; import java.ut

使用Spring来创建一个简单的工作流引擎_Java编程

文章来源:matrix 作者:Steve Dodge 摘要 spring是支持控制反转编程机制的一个相对新的框架.本文把spring作为简单工作流引擎,将它用在了更加通用的地方.在对工作流简单介绍之后,将要介绍在基本工作流场景中基于Spring的工作流API的使用. 许多J2EE应用程序要求在一个和主机分离的上下文中执行处理过程.在许多情况下,这些后台的进程执行多个任务,一些任务依赖于以前任务的状态.由于这些处理任务之间存在相互依赖的关系,使用一套基于过程的方法调用常常不能满足要求.开发人员能够

学习servlet,写一个简单的Helloword出现404错误

问题描述 学习servlet,写一个简单的Helloword出现404错误 解决方案 不需要 /servlet 解决方案二: 解决方案三: web.xml内容贴下来看看. 可以参考 我写的http://blog.csdn.net/evankaka/article/details/45151569 解决方案四: url里把[/Hello]改成[/HelloServlet] 和你Web.xml里配置的url-pattern一样. 解决方案五: 一个简单的Servlet 解决方案六: 采用servle

c++-写一个简单的二叉树遇到了segmentation fault :11问题,求助

问题描述 写一个简单的二叉树遇到了segmentation fault :11问题,求助 本人小白,写了个简单的二叉树练习一下,代码如下,运行时会出现segmentation fault :11错误,求助各位大大帮忙看看是什么原因? #include <iostream> #include <fstream> using namespace std; class Node { private: int content; Node *left; Node *right; public

checkbox-刚学完JS和servlet,写一个简单的注册登录页面

问题描述 刚学完JS和servlet,写一个简单的注册登录页面 ,想把checkbox选中的的数据和下拉列表选中的的年月日组合成字符串发送给servlet,该怎么做,求大神指教 解决方案 username: password: 表单 action 对应的servlet method 提交方式,对应servlet的doPost和doGet方法 在servlet中写 req.getParameter("username"); req.getParameter("password&

Sql存储过程游标循环的用法及sql如何使用cursor写一个简单的循环_MsSql

用游标,和WHILE可以遍历您的查询中的每一条记录并将要求的字段传给变量进行相应的处理 ================== DECLARE @A1 VARCHAR(10), @A2 VARCHAR(10), @A3 INT DECLARE CURSOR YOUCURNAME FOR SELECT A1,A2,A3 FROM YOUTABLENAME OPEN YOUCURNAME fetch next from youcurname into @a1,@a2,@a3 while @@fetch

请大神帮忙写一个简单的聚类算法程序,在线等……用matlab或者java实现

问题描述 请大神帮忙写一个简单的聚类算法程序,在线等--用matlab或者java实现 Step1:将N个数据,按照从小到大的顺序排序 d1.d2,.....dn:其中d1时最小值,dn是最大值 聚类判别阈值计算方法为: Step2:让每个数据都作为一个类,那么有 {d1},{d2},{d3},.....{dn} Step3:计算聚类中心 假设共有P个类,那么1<=k<=P,其中r代表每个类中数据的个数,同时计算相邻两个聚类中心之间的距离,如下 Step4:找出相邻两个聚类中心的最小值 Ste