一,涉及到的工程

从官网下载源码,mvn 编译成 Eclipse工程文件:

对于JMS消息这一块,主要涉及到两个工程:

oozie-core工程有问题的原因是还需要一些其他的依赖工程未导入:

二,Oozie 生成 JMS消息 主要涉及到的一些类

oozie-core 工程中的:

oozie-client工程中的:

三,相关代码:

对于Oozie Server而言,它是消息的生产者。在oozie-default.xml/oozie-site.xml里面配置好连接参数,消息服务器....Oozie就使用这些配置进行连接,产生消息,发送消息。

JMSAccessorService.java

/**
 * This class will <ul>
 * <li> Create/Manage JMS connections using user configured JNDI properties. </li>
 * <li> Create/Manage session for specific connection/topic and reconnects on failures. </li>
 * <li> Provide a way to create a subscriber and publisher </li>
 * <li> Pure JMS compliant (implementation independent but primarily tested against Apache ActiveMQ). </li>
 * </ul>
 */
public class JMSAccessorService implements Service {

直接看注释就知道这个类的功能了。

    /**
     * Map of JMS connection info to established JMS Connection
     */
    private ConcurrentMap<JMSConnectionInfo, ConnectionContext> connectionMap =
            new ConcurrentHashMap<JMSConnectionInfo, ConnectionContext>();
    /**
     * Map of JMS connection info to topic names to MessageReceiver
     */
    private ConcurrentMap<JMSConnectionInfo, Map<String, MessageReceiver>> receiversMap =
            new ConcurrentHashMap<JMSConnectionInfo, Map<String, MessageReceiver>>();

ConcurrentHashMap线程安全的,用来保存与JMS Provider的连接信息

synchronized (this) {
                if (jmsProducerConnContext == null || !jmsProducerConnContext.isConnectionInitialized()) {
                    try {
                        jmsProducerConnContext = getConnectionContextImpl();
                        jmsProducerConnContext.createConnection(connInfo.getJNDIProperties());
                        jmsProducerConnContext.setExceptionListener(new JMSExceptionListener(connInfo,
  private ConnectionContext getConnectionContextImpl() {
        Class<?> defaultClazz = conf.getClass(JMS_CONNECTION_CONTEXT_IMPL, DefaultConnectionContext.class);
        ConnectionContext connCtx = null;
        if (defaultClazz == DefaultConnectionContext.class) {
            connCtx = new DefaultConnectionContext();
        }
        else {
            connCtx = (ConnectionContext) ReflectionUtils.newInstance(defaultClazz, null);
        }
        return connCtx;
    }

创建 Producer 连接的上下文环境

DefaultConnectionContext.java  默认的连接上下文环境

public class DefaultConnectionContext implements ConnectionContext {

    protected Connection connection;
    protected String connectionFactoryName;
    private static XLog LOG = XLog.getLog(ConnectionContext.class);

    @Override
    public void createConnection(Properties props) throws NamingException, JMSException {
        Context jndiContext = new InitialContext(props);
        connectionFactoryName = (String) jndiContext.getEnvironment().get("connectionFactoryNames");
        if (connectionFactoryName == null || connectionFactoryName.trim().length() == 0) {
            connectionFactoryName = "ConnectionFactory";
        }
        ConnectionFactory connectionFactory = (ConnectionFactory) jndiContext.lookup(connectionFactoryName);
        LOG.info("Connecting with the following properties \n" + jndiContext.getEnvironment().toString());
        try {
            connection = connectionFactory.createConnection();
            connection.start();

创建生产者的方法:

    @Override
    public MessageProducer createProducer(Session session, String topicName) throws JMSException {
        Topic topic = session.createTopic(topicName);
        MessageProducer producer = session.createProducer(topic);
        return producer;
    }

它由org.apache.oozie.jms.JMSJobEventListener类中的 sendMessage()调用。

Oozie 配置中关于JMSAccessorService的配置如下:

再来看看:JMSTopicService.java

    static {
        ALLOWED_TOPIC_NAMES.add(TopicType.USER.value);
        ALLOWED_TOPIC_NAMES.add(TopicType.JOBID.value);
    }
    public static enum TopicType {
        USER("${username}"), JOBID("${jobId}");

        private String value;

        TopicType(String value) {
            this.value = value;
        }

        String getValue() {
            return value;
        }

    }

可用的Topic名称有 ${username},也可以用jobId作为Topic名称,再看Oozie官方文档解释:

The topic is obtained by concatenating topic prefix and the substituted value for topic pattern. The topic pattern can be a constant value like workflow or coordinator which the administrator has configured or ${username}.

The getJMSTopicName API can be used if the job id is already known and will give the exact topic name to which the notifications for that job are published.

 private void parseTopicConfiguration() throws ServiceException {
        String topicName = conf.get(TOPIC_NAME, "default=" + TopicType.USER.value);
        if (topicName == null) {
            throw new ServiceException(ErrorCode.E0100, getClass().getName(), "JMS topic cannot be null ");
        }

Topic默认是${username}

发送消息的实现类JMSJobEventListener.java  根据相应的作业事件发送作业的执行结果

/**
 * Class to send JMS notifications related to job events.
 *
 */
public class JMSJobEventListener extends JobEventListener {
    private JMSAccessorService jmsService = Services.get().get(JMSAccessorService.class);
    private JMSTopicService jmsTopicService = Services.get().get(JMSTopicService.class);
    private JMSConnectionInfo connInfo;
    public static final String JMS_CONNECTION_PROPERTIES = "oozie.jms.producer.connection.properties";
    public static final String JMS_SESSION_OPTS = "oozie.jms.producer.session.opts";
    public static final String JMS_DELIVERY_MODE = "oozie.jms.delivery.mode";
    public static final String JMS_EXPIRATION_DATE = "oozie.jms.expiration.date";

连接方式、发送消息后是否自动回复、消息的生命周期,持久消息还是非持久消息...

    public void init(Configuration conf) {
        LOG = XLog.getLog(getClass());
        String jmsProps = conf.get(JMS_CONNECTION_PROPERTIES);
        LOG.info("JMS producer connection properties [{0}]", jmsProps);
        connInfo = new JMSConnectionInfo(jmsProps);
        jmsSessionOpts = conf.getInt(JMS_SESSION_OPTS, Session.AUTO_ACKNOWLEDGE);
        jmsDeliveryMode = conf.getInt(JMS_DELIVERY_MODE, DeliveryMode.PERSISTENT);
        jmsExpirationDate = conf.getInt(JMS_EXPIRATION_DATE, 0);

    }

发送消息的过程:

1)EventHandlerService ,里面有个内部类EventWoker线程,当有相应的作业事件发生时,Listener被触发

**
 * Service class that handles the events system - creating events queue,
 * managing configured properties and managing and invoking various event
 * listeners via worker threads
 */
public class EventHandlerService implements Service {

//.....

public class EventWorker implements Runnable {

        @Override        public void run() {//.....other code        while (iter.hasNext()) {              try {                     if (msgType == MessageType.JOB) {                            invokeJobEventListener((JobEventListener) iter.next(), (JobEvent) event);                        }

 private void invokeJobEventListener(JobEventListener jobListener, JobEvent event) {            switch (event.getAppType()) {                case WORKFLOW_JOB:                    jobListener.onWorkflowJobEvent((WorkflowJobEvent)event);

相应的作业监听器被触发后,创建相应的作业,获得待发送的地址Topic,并序列化消息

    @Override
    public void onWorkflowJobEvent(WorkflowJobEvent event) {
        WorkflowJobMessage wfJobMessage = MessageFactory.createWorkflowJobMessage(event);
        serializeJMSMessage(wfJobMessage, getTopic(event));
    }

序列化后,调用send进行发送

    private void serializeJMSMessage(JobMessage jobMessage, String topicName) {
        MessageSerializer serializer = MessageFactory.getMessageSerializer();
        String messageBody = serializer.getSerializedObject(jobMessage);
        sendMessage(jobMessage.getMessageProperties(), messageBody, topicName, serializer.getMessageFormat());
    }

创建连接上下文、创建会话、创建消息、设置消息的属性、创建生产者、设置传送模式和消息的生命周期、然后send消息。

    protected void sendMessage(Map<String, String> messageProperties, String messageBody, String topicName,
            String messageFormat) {
        jmsContext = jmsService.createProducerConnectionContext(connInfo);
        if (jmsContext != null) {
            try {
                Session session = jmsContext.createThreadLocalSession(jmsSessionOpts);
                TextMessage textMessage = session.createTextMessage(messageBody);
                for (Map.Entry<String, String> property : messageProperties.entrySet()) {
                    textMessage.setStringProperty(property.getKey(), property.getValue());
                }
                textMessage.setStringProperty(JMSHeaderConstants.MESSAGE_FORMAT, messageFormat);
                LOG.trace("Event related JMS text body [{0}]", textMessage.getText());
                LOG.trace("Event related JMS entire message [{0}]", textMessage.toString());
                MessageProducer producer = jmsContext.createProducer(session, topicName);
                producer.setDeliveryMode(jmsDeliveryMode);
                producer.setTimeToLive(jmsExpirationDate);
                producer.send(textMessage);
                producer.close();
            }

WorkflowJobMessage.java展示了一条Workflow消息长什么样:

    /**
     * Constructor for a workflow job message
     * @param eventStatus event status
     * @param workflowJobId the workflow job id
     * @param coordinatorActionId the parent coordinator action id
     * @param startTime start time of workflow
     * @param endTime end time of workflow
     * @param status status of workflow
     * @param user the user
     * @param appName appName of workflow
     * @param errorCode errorCode of the failed wf actions
     * @param errorMessage errorMessage of the failed wf action
     */
    public WorkflowJobMessage(EventStatus eventStatus, String workflowJobId,
            String coordinatorActionId, Date startTime, Date endTime, WorkflowJob.Status status, String user,
            String appName, String errorCode, String errorMessage) {
        super(eventStatus, AppType.WORKFLOW_JOB, workflowJobId, coordinatorActionId, startTime,
                endTime, user, appName);
        this.status = status;
        this.errorCode = errorCode;
        this.errorMessage = errorMessage;
    }

当提交的是Workflow Job,就会生成Workflow消息。

它有一个属性: @param coordinatorActionId the parent coordinator action id  (Coordinator Job里面的Action是Workflow Job)

看完了JMS消息体,再来看看消息头:

/**
 *
 * Class holding constants used in JMS selectors
 */
public final class JMSHeaderConstants {
    // JMS Application specific properties for selectors
    public static final String EVENT_STATUS = "eventStatus";
    public static final String SLA_STATUS = "slaStatus";
    public static final String APP_NAME = "appName";
    public static final String USER = "user";
    public static final String MESSAGE_TYPE = "msgType";
    public static final String APP_TYPE = "appType";

    public static final String JOBID = "jobId";// add for my specific selectors
    // JMS Header property
    public static final String MESSAGE_FORMAT = "msgFormat";
}

消息头里面的属性主要用来过滤。根据消息头里面的字段,使用JMS消息选择器对消息进行过滤。关于根据JobId进行过滤,可参考:Oozie JMS通知消息实现--根据作业ID来过滤消息

不知道有没有bug????

/**
 * Message deserializer to convert from JSON to java object
 */
public class JSONMessageDeserializer extends MessageDeserializer {

    static ObjectMapper mapper = new ObjectMapper(); // Thread-safe.

    static {
        mapper.configure(DeserializationConfig.Feature.FAIL_ON_UNKNOWN_PROPERTIES, false);
    }

消息的序列化机制,用的是jackson-mapper-asl jar包。因为消息要从生产者发给消息服务器,就需要序列化了。

有序列化就有反序列化:

/**
 * Class to deserialize the jms message to java object
 */
public abstract class MessageDeserializer {

    /**
     * Constructs the event message from JMS message
     *
     * @param message the JMS message
     * @return EventMessage
     * @throws JMSException
     */
    @SuppressWarnings("unchecked")
    public <T extends EventMessage> T getEventMessage(Message message) throws JMSException {
        TextMessage textMessage = (TextMessage) message;
        String appTypeString = textMessage.getStringProperty(JMSHeaderConstants.APP_TYPE);
        String msgType = textMessage.getStringProperty(JMSHeaderConstants.MESSAGE_TYPE);

先根据消息的属性解析出消息的类型。

        if (MessageType.valueOf(msgType) == MessageType.JOB) {
            switch (AppType.valueOf(appTypeString)) {
                case WORKFLOW_JOB:
                    WorkflowJobMessage wfJobMsg = getDeserializedObject(messageBody, WorkflowJobMessage.class);
                    wfJobMsg.setProperties(textMessage);
                    eventMsg = (T) wfJobMsg;

再根据类型来构造对象。

Oozie 生成JMS消息并向 JMS Provider发送消息过程分析的更多相关文章

  1. 2.技巧: 用 JAXM 发送和接收 SOAP 消息—Java API 使许多手工生成和发送消息方面必需的步骤自动化

    转自:https://www.cnblogs.com/chenying99/archive/2013/05/23/3094128.html 技巧: 用 JAXM 发送和接收 SOAP 消息—Java ...

  2. SPRING 集成 KAFKA 发送消息

    准备工作 1.安装kafka+zookeeper环境 2.利用命令创建好topic,创建一个topic my-topic 集成步骤 1.配置生产者 <?xml version="1.0 ...

  3. Java企业微信开发_05_消息推送之发送消息(主动)

    一.本节要点 1.发送消息与被动回复消息 (1)流程不同:发送消息是第三方服务器主动通知微信服务器向用户发消息.而被动回复消息是 用户发送消息之后,微信服务器将消息传递给 第三方服务器,第三方服务器接 ...

  4. Java企业微信开发_04_消息推送之发送消息(主动)

    源码请见: Java企业微信开发_00_源码及资源汇总贴 一.本节要点 1.发送消息与被动回复消息 (1)流程不同:发送消息是第三方服务器主动通知微信服务器向用户发消息.而被动回复消息是 用户发送消息 ...

  5. MSMQ向远程服务器发送消息----错误总结

    一:路径错误(Path)错误 如果向远程服务器发送消息,请使用格式名的形式,如: FormatName:Direct=TCP:121.0.0.1\\private$\\queueFormatName: ...

  6. RabbitMQ学习系列二-C#代码发送消息

    RabbitMQ学习系列二:.net 环境下 C#代码使用 RabbitMQ 消息队列 http://www.80iter.com/blog/1437455520862503 上一篇已经讲了Rabbi ...

  7. Java企业微信开发_05_消息推送之被动回复消息

    一.本节要点 1.消息的加解密 微信加解密包 下载地址:http://qydev.weixin.qq.com/java.zip      ,此包中封装好了AES加解密方法,直接调用方法即可. 其中,解 ...

  8. 新浪微博发送消息和授权机制原理(WeiboSDK)

    1.首先是在微博发送消息,对于刚開始做weibo发送消息的刚開始学习的人会有一个误区,那就是会觉得须要授权后才干够发送消息.事实上发送消息仅仅须要几行代码就能够实现了,很easy,不须要先授权再发送消 ...

  9. Kafka学习笔记(6)----Kafka使用Producer发送消息

    1. Kafka的Producer 不论将kafka作为什么样的用途,都少不了的向Broker发送数据或接受数据,Producer就是用于向Kafka发送数据.如下: 2. 添加依赖 pom.xml文 ...

随机推荐

  1. Azure : 通过 SendGrid 发送邮件

    SendGrid 是什么? SendGrid 是架构在云端的电子邮件服务,它能提供基于事务的可靠的电子邮件传递.并且具有可扩充性和实时分析的能力.常见的用例有:1. 自动回复用户的邮件2. 定期发送信 ...

  2. 网站遭受大量CC攻击后的应对策略

    上周开始我网站遭受了一大波CC攻击,到目前为止仍在继续,作为一个建站小白,我感觉压力好大,又有新的问题要挑战了! 服务器架设在腾讯云,CC攻击很凶猛,带宽瞬间占满,于是在腾讯云后台配置安全组关闭了80 ...

  3. 实训三(cocos2dx 3.x 打包apk)

    上一篇文章<实训二(cocos2dx 2.x 打包apk)>简单的讲述的利用cocos2dx 2.x引擎在windows平台上打包apk的方法与过程,本文将介绍3.x版本引擎,如何打包ap ...

  4. 云平台项目--学习经验--BootstrapValidate表单验证插件

    使用前提,需要加载jquery和bootstrap库.并且引入bootstrapValidator.js和bootstrapValidator.css文件然后建立一个form表单,添加表单控件,表单控 ...

  5. Django 图片上传、存储与显示

    参考博客:http://www.cognize.me/2016/05/09/djangopic 开始之前要先安装python图像处理库:pip install --use-wheel Pillow 一 ...

  6. NetScaler 10.1的配置以及结合StoreFront的部署

    工作需要,所以英文+中文,绝壁不是装逼...(关于这点勿喷) This post will cover only the basics for getting NetScaler up and run ...

  7. Linux用户管理机制

    Linux系统中的用户管理涉及用户账号文件 /etc/passwd.用户密码文件 /etc/shadow.用户组文件 /etc/group. 一.用户账号文件 /etc/passwd 该文件为纯文本文 ...

  8. CentOS下面磁盘扩容处理

    1. 给虚拟机增加一块硬盘: 过程不表 2. 增加了硬盘之后需要重启一下 查看磁盘 ls /dev/sd* 3. 使用  gdisk 处理磁盘 注意 这里面fdisk 貌似没法处理成 LVM 必须使用 ...

  9. msyql: navicat 连接时msyql遇到的问题

    1.使用 mysql的用户,密码连接 服务器上的MySQL时,连接不上,报 100xx的错误. 1)原因是,MySQL默认情况下,只允许本地连接[127.0.0.1,或localhost]来连接mys ...

  10. Hbase之JAVA API不能远程访问问题解决

    1.配置Linux的hostname2.配置Linux的hosts,映射ip的hostname的关系3.配置访问windows的hosts 参考文档:http://blog.csdn.net/ty49 ...