最近半年多一直在做hive相关的开发工作,并且使用Oozie做为hive工作流的引擎,用于管理Hadoop任务。Oozie的任务流包括:croodinator、workflow。workflow用于描述任务的执行顺序,croodinator用于定义oozie的定时任务。workflow定义了两种结点:

  • 控制流结点:主要包括start、end、fork、join等,其中fork、join成对出现,在fork展开。分支,最后在join结点汇聚。
  • Action结点:包括Hadoop任务、SSH、HTTP、EMAIL、OOZIE子任务等。
workflow.xml用于配置workflow任务动作,当job的脚本较多,解读起来比较困难,并且出现并发的时,解析就更困难了。近期在做hadoop的旧job的优化,涉及比较多Job,其中大多数,都是其他同事开发的,而workflow的解读又工作过程中不得不面对的繁琐工作。于是闲暇之余,写了一个workflow.xml文件解析工具:输入job的名称,能显示该job的流程图。需要解决的问题主要有两方面 :
  1. job的workflow.xml文件的读取、解析。
  2. 结点视图的绘制。
xml文件的解析,使用dom4j包就能轻松解决。workflow文件的可以直接从svn主干上读取。由于本人对基于j2ee的web开发比较熟悉,最终决定用网页展示结点视图。网上找了个html的绘图插件raphael,用于网页上失量图形。编程思路有了,下面是开始代码的实现,整个流程如下:
  1. svn job代码下载:svn主要的代码不定期的会从其分支合入新代码,需要写一个定时器,每天去全量的同步svn的代码。
  2. dom4j解析workflow.xml,抽象结点对象,视图数据准备阶段。
  3. 将视力数据利用Freemarker模板工具,解析到客户端,客户端根据结点数据,绘制结点。
job搜索
为了尽量简化用户的操作,在网而上做了一个搜索job的功能,使用jquery 的autocomplete方法实现:当用户输入job关键字时,模糊搜索svn上的的job,显示配置上的job,简化用户输入。

workflow数据解析
编码的核心工作是第2步,xml文件的解析,使用dom4j很容易就能将workflow.xml解析成结点的链表。每个结点,保存着下一步要执行的结点链表。困难的是,要把链表数据,转化视图展示的坐标,输出到客户端。我们不妨将50px视为一个坐标单位,结点只显示边框,在边框中显示结点名,并且结点与结点之前横向与纵向都间隔一个单位。下面分享我的坐标转化算法:
结点的高度占一个单位,宽度与结点名文本长度相关(5个字符占一个单位)。利用递归算法,从开始结点遍历结点列表,直到结束结点。我们不妨将结点的上一步执行结点叫结点的前结点,下一步执行结点,叫做结点的后结点。
 结点横坐标=MAX(前结点的横坐标+结点宽度+1)
结点纵坐标=MAX(前结点的纵坐标)
使用递归的算法,当视图中加入新的结点时,可能会引起视图中的结点的前结点、后结点个数的改变,需要重新调整结点的横、纵坐标。

代码实现
核心代码如下:
OozieHelper.java
package com.lxr.oozie.workflow;import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;import org.dom4j.Document;
import org.dom4j.Element;public class OozieHelper {private Document document;private Element root = null;private List<WorkflowNode> workflowNodes = new ArrayList<WorkflowNode>();private Map<String, WorkflowNode> workflowNodeMap = new HashMap<String, WorkflowNode>();public OozieHelper(Document document) {this.document = document;}private Element getNode(String tag) {return root.element(tag);}private Element getNode(String attr, String value) {List<Element> nodes = root.elements();for (Element el : nodes) {if (value.equals(el.attributeValue(attr))) {return el;}}return null;}private Element getNodeByName(String name) {return getNode("name", name);}private String getNextNodeName(Element node) {String tagName = node.getName();if ("action".equals(tagName)) {Element element = node.element("ok");return element.attributeValue("to");} else {return node.attributeValue("to");}}private List<Element> getNextNodes(Element node) {if (null == node) {return null;}List<Element> nextNodes = new ArrayList<Element>();String nextNodeName = getNextNodeName(node);if (null != nextNodeName) {Element nextNode = getNodeByName(nextNodeName);String tagName = nextNode.getName();if ("fork".equalsIgnoreCase(tagName)) {List<Element> elements = nextNode.elements();for (Element el : elements) {nextNodeName = el.attributeValue("start");if (null != nextNodeName) {nextNode = getNodeByName(nextNodeName);if (null != nextNode) {nextNodes.add(nextNode);}}}} else if ("join".equals(tagName)) {nextNode = getNodeByName(nextNode.attributeValue("to"));nextNodes.add(nextNode);} else {nextNodes.add(nextNode);}}return nextNodes;}private void adjustToAddNode(WorkflowNode workflowNode) {for (WorkflowNode node : workflowNodes) {if (node.equals(workflowNode)) {return;}}workflowNodes.add(workflowNode);}private void genNextWorkflowNodes(WorkflowNode parent) {List<Element> nextNodes = getNextNodes(parent.getElement());if (0 != nextNodes.size()) {for (int i = 0, len = nextNodes.size(); i < len; i++) {Element el = nextNodes.get(i);String nodeName = el.attributeValue("name");WorkflowNode subWorkflowNode = workflowNodeMap.get(nodeName);int subX = parent.getX() + parent.getLength() + 1;int subY = parent.getY() + 2;if (null == subWorkflowNode) {subWorkflowNode = new WorkflowNode(el);subWorkflowNode.setName(nodeName);subWorkflowNode.setX(subX);subWorkflowNode.setY(subY);genNextWorkflowNodes(subWorkflowNode);workflowNodes.add(subWorkflowNode);// adjustToAddNode(subWorkflowNode);} else {subWorkflowNode.setX(Math.max(subX, subWorkflowNode.getX()));if (subY > subWorkflowNode.getY()) {subWorkflowNode.adjustNextNodesY(subY - subWorkflowNode.getY());subWorkflowNode.setY(subY);}}subWorkflowNode.previousNodes().add(parent);parent.nextNodes().add(subWorkflowNode);workflowNodeMap.put(nodeName, subWorkflowNode);}}}public List<WorkflowNode> parse() {root = document.getRootElement();Element startNode = getNode("start");if (null != startNode) {WorkflowNode start = new WorkflowNode(startNode);start.setName("start");start.setX(0);start.setY(0);workflowNodes.add(start);genNextWorkflowNodes(start);} else {System.out.println("未找到开始结点。");}return workflowNodes;}
}

view-workflow.html

<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Oozie Workflow - ${jobName}</title>
<script type="text/javascript" src="../js/jquery.min.js"></script>
<script type="text/javascript" src="../js/raphael/raphael.min.js"></script>
<script type="text/javascript" src="../js/raphael/raphael.ext.js"></script>
<script type="text/javascript" src="../js/oozie.wf.draw.js"></script>
</head><body><label style="position: absolute;">job名称:${jobName}</label><div class="workflow-holder" id="${jobName}" margin-left="100"margin-top="100" grid-padding-left="15" grid-padding-top="10"grid-width="50" grid-height="25"><#list nodes as node><div id="${node.name}" class="branch-node workflow-node"xx="${node.x}" yy="${node.y}" length="${node.length}"next-node="${node.nextJobNames!}">${node.name}</div></#list></div>
</body>
</html>

oozie.wf.draw.js

jQuery.fn.attrn = function(attr) {return parseInt($(this).attr(attr));
}HTMLDivElement.prototype.$$ = function(attr) {return $(this).attr(attr);
}HTMLDivElement.prototype.$$n = function(attr) {return parseInt(this.$$(attr));
}$(function() {var $holder = $('.workflow-holder');var $nodes = $holder.find('.workflow-node');var nodes = [];$nodes.each(function(i, el) {nodes[i] = {id : $(el).attr('id'),index : i,xx : el.$$n('xx'),yy : el.$$n('yy'),length: el.$$n('length'),$instance : $(el)}});var workflowCfg = {id : $holder.attr('id'),margin : {left : $holder.attrn('margin-left'),top : $holder.attrn('margin-top')},grid : {paddingLeft : $holder.attrn('grid-padding-left'),paddingTop : $holder.attrn('grid-padding-top'),width : $holder.attrn('grid-width'),height : $holder.attrn('grid-height')},nodes : nodes,nodesMap : (function() {var map = {};for (var i = 0; i < nodes.length; i++) {var node = nodes[i];map[node.$instance.attr('id')] = node;}return map;})()};console.log(workflowCfg)// 用来存储节点的顺序var connections = [];// 拖动节点开始时的事件var dragger = function() {this.ox = this.attr('x');this.oy = this.attr('y');this.animate({'fill-opacity' : .2}, 500);};// 拖动事件var move = function(dx, dy) {var att = {x : this.ox + dx,y : this.oy + dy};this.attr(att);$holder.find("#" + this.id).offset({top : this.oy + dy + workflowCfg.grid.paddingTop,left : this.ox + dx + workflowCfg.grid.paddingLeft});for (var i = connections.length; i--;) {r.drawArr(connections[i]);}};// 拖动结束后的事件var up = function() {this.animate({'fill-opacity' : 0}, 500);};// 创建绘图对象var r = Raphael(workflowCfg.id, $(window).width(), $(window).height());// 绘制节点var shapes = [];var maxRight = 0;for (var i = 0, len = workflowCfg.nodes.length; i < len; i++) {var node = workflowCfg.nodes[i];node.left = workflowCfg.margin.left + node.xx * workflowCfg.grid.width;node.top = workflowCfg.margin.top + node.yy * workflowCfg.grid.height;node.width = workflowCfg.grid.width * node.length;node.height = workflowCfg.grid.height;shapes[i] = r.rect(node.left, node.top, node.width, node.height, 4);// 定位节点上的文字node.$instance.offset({top : node.top + workflowCfg.grid.paddingTop,left : node.left + workflowCfg.grid.paddingLeft});var right = node.$instance.offset().left + node.width;maxRight = maxRight > right? maxRight : right;}var $svg = $holder.find('svg');var svnWidth = maxRight + workflowCfg.grid.paddingLeft;var _svnWidth = $svg.attrn('width');$svg.attr('width', svnWidth > _svnWidth? svnWidth : _svnWidth);// 为节点添加样式和事件,并且绘制节点之间的箭头for (var i = 0, ii = shapes.length; i < ii; i++) {var color = Raphael.getColor();shapes[i].attr({fill : color,stroke : color,'fill-opacity' : 0,'stroke-width' : 2,cursor : 'move'});shapes[i].id = workflowCfg.nodes[i].id;shapes[i].drag(move, dragger, up);shapes[i].dblclick(function() {alert(this.id)})}// 节点连线for (var i = 0; i < workflowCfg.nodes.length; i++) {var node = workflowCfg.nodes[i];var nextNodeIds = node.$instance.attr('next-node');if (nextNodeIds) {var nextNodeIdArr = nextNodeIds.split(',');for (var j = 0; j < nextNodeIdArr.length; j++) {var nextNodeId = nextNodeIdArr[j];var nextNode = workflowCfg.nodesMap[nextNodeId];connections.push(r.drawArr({obj1 : shapes[node.index],obj2 : shapes[nextNode.index]}));}}}
});
运行效果如下:

Oozie workflow.xml 视图解析相关推荐

  1. 详解Spring MVC 4之ViewResolver视图解析器

    所有的We MVC框架都有一套它自己的解析视图的机制,Spring MVC也不例外,它使用ViewResolver进行视图解析,让用户在浏览器中渲染模型.ViewResolver是一种开箱即用的技术, ...

  2. php xml expat,PHP XML Expat 解析器

    PHP XML Expat 解析器 内建的 Expat 解析器使在 PHP 中处理 XML 文档成为可能. XML 是什么? XML 用于描述数据,其焦点是数据是什么.XML 文件描述了数据的结构. ...

  3. 几种常见的ViewResolver视图解析器

    1.几种常见的ViewResolver视图解析器 在Spring MVC 4控制器中,所有的处理方法必须返回一个逻辑视图名称,无论是显式的(返回String,View或ModelAndView)还是隐 ...

  4. 基于XML配置的Spring MVC(所需jar包,web.xml配置,处理器配置,视图解析器配置)

    1.添加jar 2.web.xml配置 <?xml version="1.0" encoding="UTF-8"?> <web-app ver ...

  5. Spring 视图解析

    作为一个菜鸟的程序员,经常看到一些大神在使用Spring的,各种各样的配置文件,也不是很理解 视图解析器,一开始的时候我们也是跟着别人进行配置,比着葫芦画瓢,今天也算是对视图解析器有了一个小小的理解 ...

  6. 学习SpringMVC——说说视图解析器

    各位前排的,后排的,都不要走,咱趁热打铁,就这一股劲我们今天来说说spring mvc的视图解析器(不要抢,都有位子~~~) 相信大家在昨天那篇如何获取请求参数篇中都已经领略到了spring mvc注 ...

  7. springmvc十六:视图解析

    spring.xml配置视图解析器如下: <?xml version="1.0" encoding="UTF-8"?> <beans xmln ...

  8. 管理springmvc组件——前端控制器、控制器映射器和适配器、视图解析器、文件上传的、拦截器||消息转化

    管理springmvc组件 概述 在使用springmvc时要配置哪些东西 前端控制器 控制器映射器和适配器 映射器  Map<Set<String>,Object> Set& ...

  9. SpringMVC 中整合JSON、XML视图一

    SpringMVC中整合了JSON.XML的视图,可以通过这些视图完成Java对象到XML.JSON的转换.转换XML提供了MarshallingView,开发者只需用注入相应的marshaller. ...

最新文章

  1. html表格td宽度设置,table以及td宽度设置细节
  2. BCH两周年独立日已至,世界各地爱好者纷纷举办Meetup庆祝
  3. 配置Cisco ASA and Cisco *** Client 4.x with Windows 2003 IAS RADIUS Authentication
  4. 《零基础入门学习Python》学习过程笔记【013元组】
  5. 密码篇——对称加密—3DES
  6. javascript中 this 指向问题
  7. MATLAB作图方法与技巧(二)
  8. spring注解方式 idea报could not autowire
  9. 让Python中类的属性具有惰性求值的能力
  10. 3_6 CommandMode 命令模式
  11. linux进程看门狗使用方式,Linux系统中基于看门狗的精细化进程监控方法及系统的制作方法...
  12. 前端学习(2933):vue中的循环语句
  13. 安卓下设置系统字体大小影响H5页面布局
  14. 【数据结构】栈的基本操作
  15. 巴斯勒相机的ip掩码_相机IP和带宽设置
  16. 大数据集群监控体系架构
  17. 面试题:为什么说 Mybatis 是半自动ORM 映射工具?它与全自动的区别在哪里?
  18. 怎么看电脑支持多少兆网速_怎么看电脑网卡多少兆_如何查看网卡多少兆-系统城...
  19. android 日历动画的实现
  20. 河北官方:邯郸涉县致4死5伤煤气泄漏事故涉嫌瞒报

热门文章

  1. Nacos,一款非常优秀的注册中心(附视频)
  2. 王选:科研成功应具备的要素
  3. 低代码合作开发的3个竞争优势是什么?
  4. 一灯大师,基于imx6ull点亮LED灯
  5. clang format
  6. 苹果手机群控免越狱手机投屏
  7. VML极道教程(十二) VML编程大结局
  8. Eclipse 暗黑主题风格
  9. 退休教师养老金月8千,有人介绍一份看大门工作月资2千,去不去?
  10. Windows使用uncompyle6