[置顶] [4]AMQP(高级消息队列协议) ----改造QPID JMS-0.9 API 对 SSLContext的 支持

标签: AMQPQPIDSSLContextSSL
824人阅读 评论(0) 收藏 举报
分类:

默认情况下QPID JMS-0.9 API对SSLConext没有提供相应的接口. 但是在笔者项目中,又需要其对SSLContext对象的支持。这个问问深深的难住了笔者2~3天时间,笔者通过深入的研究和分析代码,终于找到了破解之道。 

首先QPID JMS-0.9 API的源代码可以从https://qpid.apache.org/releases/qpid-jms-0.9.0/index.html 这个地址下载到。

而且其调用SSL的代码流程如下:

org.apache.qpid.jms.JmsConnectionFactory--->createConnection()
org.apache.qpid.jms.JmsConnectionFactory--->createProvider()
org.apache.qpid.jms.provider.amqp.AmqpProvider--->connect()
org.apache.qpid.jms.transports.TransportFactory -->createTransport()
org.apache.qpid.jms.transports.netty.NettySslTransport--->configureChannel() 
org.apache.qpid.jms.transports.TransportSupport  -->createSslHandler()

所以现在的关键是改造 org.apache.qpid.jms.transports.TransportSupport类中的creatSslHandler()方法,能把相应SSLContext传递进去. 所以需要自定一个自己的

一个JmsInitialContextFactory做为整个程序的入口

#1 CustomJmsInitialContextFactory

package org.apache.qpid.jms.jndi;

import java.util.Map;

import org.apache.qpid.jms.CustomJmsConnectionFactory;
import org.apache.qpid.jms.JmsConnectionFactory;

public class CustomJmsInitialContextFactory extends JmsInitialContextFactory {
	protected JmsConnectionFactory createConnectionFactory(Map<String, String> properties) {
        JmsConnectionFactory factory = new CustomJmsConnectionFactory();
        Map<String, String> unused = factory.setProperties(properties);
        if (!unused.isEmpty()) {
            String msg =
                  " Not all properties could be set on the ConnectionFactory."
                + " Check the properties are spelled correctly."
                + " Unused properties=[" + unused + "].";
            throw new IllegalArgumentException(msg);
        }

        return factory;
    }
}

#2 通过自定义的CustomJmsInitialContextFactory的引用自定义的CustomJmsConnectionFactory

package org.apache.qpid.jms;

import java.net.URI;

import org.apache.qpid.jms.provider.CustomProviderFactory;
import org.apache.qpid.jms.provider.Provider;
import org.apache.qpid.jms.provider.ProviderFactory;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class CustomJmsConnectionFactory extends JmsConnectionFactory {
	private static final Logger LOG = LoggerFactory.getLogger(CustomJmsConnectionFactory.class);
	 protected Provider createProvider(URI remoteURI) throws Exception {
        if (remoteURI == null) {
            remoteURI = new URI(getDefaultRemoteAddress());
        }
        Provider result = null;
        try {
            //AmqpProvider
            result = CustomProviderFactory.create(remoteURI);
            result.connect();
        } catch (Exception ex) {
            LOG.error("Failed to create JMS Provider instance for: {}", remoteURI.getScheme());
            LOG.trace("Error: ", ex);
            throw ex;
        }
       
        return result;
    }
}


#3 通过CustomJmsConnectionFactory的调用自定义的CustomProviderFactory

package org.apache.qpid.jms.provider;

import java.io.IOException;
import java.net.URI;

import org.apache.qpid.jms.provider.amqp.CustomAmqpProviderFactory;
import org.apache.qpid.jms.provider.failover.CustomFailoverProviderFactory;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public abstract class CustomProviderFactory extends ProviderFactory {
	 private static final Logger LOG = LoggerFactory.getLogger(CustomProviderFactory.class);
	 private static boolean isFailoverSSL(String url){
		 boolean isFailoverSSL=false;
		 url=url.trim().toLowerCase();
		 if(url.startsWith("failover")){
			 url=url.replace("failover:(", "");
			 url=url.replace(")", "");
			 if(url.startsWith("amqps")){
				 isFailoverSSL=true;
			 }
		 }
		 return isFailoverSSL;
	 }
	 public static ProviderFactory findProviderFactory(URI location) throws IOException {
		   ProviderFactory factory = null;
	        String scheme = location.getScheme();
	        if("amqps".equalsIgnoreCase(scheme)){
	        	CustomAmqpProviderFactory  customAmqpProviderFactory=new CustomAmqpProviderFactory();
	        	customAmqpProviderFactory.setTransportType("ssl");
	        	return  customAmqpProviderFactory;
	        }else if(isFailoverSSL(location.toString())){
	        	CustomFailoverProviderFactory customFailoverProviderFactory= new CustomFailoverProviderFactory();
	        	return customFailoverProviderFactory;
	        }
	        else{
	        	factory=ProviderFactory.findProviderFactory(location);
	        	return factory;
	        }
	      
	       
	    }
	 /**
     * Static create method that performs the ProviderFactory search and handles the
     * configuration and setup.
     *
     * @param remoteURI
     *        the URI of the remote peer.
     *
     * @return a new AsyncProvider instance that is ready for use.
     *
     * @throws Exception if an error occurs while creating the AsyncProvider instance.
     */
    public static Provider create(URI remoteURI) throws Exception {
    	 
        Provider result = null;

        try {
            ProviderFactory factory = CustomProviderFactory.findProviderFactory(remoteURI);
            result = factory.createProvider(remoteURI);
        } catch (Exception ex) {
            LOG.error("Failed to create Provider instance for {}, due to: {}", remoteURI.getScheme(), ex);
           LOG.trace("Error: ", ex);
           throw ex;
        }

        return result;
   }

}

#4 通过CustomProviderFactory的调用自定义的CustomAmqpProviderFactory
package org.apache.qpid.jms.provider.amqp;

import java.net.URI;
import java.util.Map;

import org.apache.qpid.jms.provider.Provider;
import org.apache.qpid.jms.provider.amqp.AmqpProvider;
import org.apache.qpid.jms.provider.amqp.AmqpProviderFactory;
import org.apache.qpid.jms.util.PropertyUtil;

public class CustomAmqpProviderFactory extends AmqpProviderFactory {
	 @Override
	    public Provider createProvider(URI remoteURI) throws Exception {

	        Map<String, String> map = PropertyUtil.parseQuery(remoteURI.getQuery());
	        Map<String, String> providerOptions = PropertyUtil.filterProperties(map, "amqp.");

	        remoteURI = PropertyUtil.replaceQuery(remoteURI, map);
      
	        AmqpProvider result =null;
	       
	        if("ssl".equalsIgnoreCase(this.getTransportType())){
	          result= new CustomAmqpProvider(remoteURI);
	        }else{
	          result=new AmqpProvider(remoteURI);
	        }
	        result.setTransportType(getTransportType());

	        Map<String, String> unused = PropertyUtil.setProperties(result, providerOptions);
	        if (!unused.isEmpty()) {
	            String msg = ""
	                + " Not all provider options could be set on the AMQP Provider."
	                + " Check the options are spelled correctly."
	                + " Unused parameters=[" + unused + "]."
	                + " This provider instance cannot be started.";
	            throw new IllegalArgumentException(msg);
	        }

	        return result;
	    }
}



#5 通过CustomAmqpProviderFactory的调用自定义的CustomAmqpProvider

package org.apache.qpid.jms.provider.amqp;

import java.io.IOException;
import java.net.URI;

import org.apache.qpid.jms.transports.TransportFactory;
import org.apache.qpid.jms.util.IOExceptionSupport;
import org.apache.qpid.jms.utils.ReflectionUtils;
import org.apache.qpid.jms.provider.amqp.AmqpProvider;
import org.apache.qpid.jms.transports.CustomTransportFactory;
import org.apache.qpid.jms.transports.Transport;
public class CustomAmqpProvider extends AmqpProvider{

	public CustomAmqpProvider(URI remoteURI) {
		super(remoteURI);
	}
	
	@Override
	public void connect() throws IOException {
		    ReflectionUtils.invokeMethod(this, "checkClosed", null, null);
		  
	        try {
	        	Transport transport=null;
	        	if("ssl".equals(getTransportType())){
	        		 transport = CustomTransportFactory.create(getTransportType(), getRemoteURI());
	        	}else {
	        	    transport = TransportFactory.create(getTransportType(), getRemoteURI());
	        	}
	        	ReflectionUtils.setFieldValue(this, "transport", transport);
	        } catch (Exception e) {
	            throw IOExceptionSupport.create(e);
	        }
	       ((Transport)ReflectionUtils.getFieldValue(this, "transport")).setTransportListener(this); 
	       ((Transport)ReflectionUtils.getFieldValue(this, "transport")).connect();   
   }
	/*
	 @Override
	    public void connect() throws IOException {
	        checkClosed();
	        try {
	            transport = TransportFactory.create(getTransportType(), getRemoteURI());
	        } catch (Exception e) {
	            throw IOExceptionSupport.create(e);
	        }
	        transport.setTransportListener(this);
	        //org.apache.qpid.jms.transports.netty.NettySslTransport
	        transport.connect();
	    }
	   */
}

# 6 通过CustomAmqpProvider的调用自定义的CustomTransportFactory

package org.apache.qpid.jms.transports;

import java.net.URI;
import java.util.Map;

import org.apache.qpid.jms.transports.netty.CustomNettySslTransportFactory;
import org.apache.qpid.jms.util.PropertyUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public abstract class CustomTransportFactory extends TransportFactory {
    private static final Logger LOG = LoggerFactory.getLogger(CustomTransportFactory.class);
    public Transport createTransport(URI remoteURI) throws Exception {
        Map<String, String> map = PropertyUtil.parseQuery(remoteURI.getQuery());
        Map<String, String> transportURIOptions = PropertyUtil.filterProperties(map, "transport.");

        remoteURI = PropertyUtil.replaceQuery(remoteURI, map);

        TransportOptions transportOptions = doCreateTransportOptions();

        Map<String, String> unused = PropertyUtil.setProperties(transportOptions, transportURIOptions);
        if (!unused.isEmpty()) {
            String msg = " Not all transport options could be set on the " + getName() +
                         " Transport. Check the options are spelled correctly." +
                         " Unused parameters=[" + unused + "]." +
                         " This provider instance cannot be started.";
            throw new IllegalArgumentException(msg);
        }

        Transport result = doCreateTransport(remoteURI, transportOptions);

        return result;
    }
	public static Transport create(String transportKey, URI remoteURI) throws Exception {
        Transport result = null;

        try {
            TransportFactory factory = new CustomNettySslTransportFactory();
            result = factory.createTransport(remoteURI);
        } catch (Exception ex) {
            LOG.error("Failed to create Transport instance for {}, due to: {}", remoteURI.getScheme(), ex);
            LOG.trace("Error: ", ex);
            throw ex;
        }

        return result;
    }
}

# 7 通过CustomTransportFactory的调用自定义的CustomNettySslTransportFactory

package org.apache.qpid.jms.transports.netty;

import java.net.URI;

import org.apache.qpid.jms.transports.TransportOptions;

public class CustomNettySslTransportFactory extends NettySslTransportFactory{
    @Override
    protected NettyTcpTransport doCreateTransport(URI remoteURI, TransportOptions transportOptions) throws Exception {
        return new CustomNettySslTransport(remoteURI, transportOptions);
    }

}

# 8 通过CustomNettySslTransportFactory的调用自定义的CustomNettySslTransport

package org.apache.qpid.jms.transports.netty;

import java.net.URI;
import java.util.concurrent.atomic.AtomicBoolean;

import org.apache.qpid.jms.transports.CustomTransportSupport;
import org.apache.qpid.jms.transports.TransportListener;
import org.apache.qpid.jms.transports.TransportOptions;
import org.apache.qpid.jms.transports.TransportSslOptions;
import org.apache.qpid.jms.transports.TransportSupport;
import org.apache.qpid.jms.util.IOExceptionSupport;
import org.apache.qpid.jms.utils.ReflectionUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import io.netty.buffer.ByteBuf;
import io.netty.channel.Channel;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler;
import io.netty.handler.ssl.SslHandler;
import io.netty.util.concurrent.Future;
import io.netty.util.concurrent.GenericFutureListener;

public class CustomNettySslTransport extends NettySslTransport{
    private static final Logger LOG = LoggerFactory.getLogger(CustomNettySslTransport.class);
	public CustomNettySslTransport(URI remoteLocation, TransportOptions options) {
		super(remoteLocation, options);
	}

	public CustomNettySslTransport(TransportListener listener, URI remoteLocation, TransportOptions options) {
		super(listener, remoteLocation, options);
	}
	
	 @Override
	    protected void configureChannel(final Channel channel) throws Exception {
	        SslHandler sslHandler = CustomTransportSupport.createSslHandler(getRemoteLocation(), ((TransportSslOptions)ReflectionUtils.invokeMethod(this, "getSslOptions", null, null)));
	        sslHandler.handshakeFuture().addListener(new GenericFutureListener<Future<Channel>>() {
	            @Override
	            public void operationComplete(Future<Channel> future) throws Exception {
	                if (future.isSuccess()) {
	                    LOG.trace("SSL Handshake has completed: {}", channel);
	                    connectionEstablished(channel);
	                } else {
	                    LOG.trace("SSL Handshake has failed: {}", channel);
	                    connectionFailed(channel, IOExceptionSupport.create(future.cause()));
	                }
	            }
	        });
	        channel.pipeline().addLast(sslHandler);
	        channel.pipeline().addLast(new CustomNettyTcpTransportHandler());
	       
	    }
	 
	 private class CustomNettyTcpTransportHandler extends SimpleChannelInboundHandler<ByteBuf> {

	        @Override
	        public void channelActive(ChannelHandlerContext context) throws Exception {
	            LOG.trace("Channel has become active! Channel is {}", context.channel());
	        }

	        @Override
	        public void channelInactive(ChannelHandlerContext context) throws Exception {
	            LOG.trace("Channel has gone inactive! Channel is {}", context.channel());
	            if (((AtomicBoolean)ReflectionUtils.getFieldValue(CustomNettySslTransport.this, "connected")).compareAndSet(true, false) && !((AtomicBoolean)ReflectionUtils.getFieldValue(CustomNettySslTransport.this, "closed")).get()) {
	                LOG.trace("Firing onTransportClosed listener");
	                listener.onTransportClosed();
	            }
	        }

	        @Override
	        public void exceptionCaught(ChannelHandlerContext context, Throwable cause) throws Exception {
	            LOG.trace("Exception on channel! Channel is {}", context.channel());
	            if (((AtomicBoolean)ReflectionUtils.getFieldValue(CustomNettySslTransport.this, "connected")).compareAndSet(true, false) && !((AtomicBoolean)ReflectionUtils.getFieldValue(CustomNettySslTransport.this, "closed")).get()) {
	                LOG.trace("Firing onTransportError listener");
	                if (((Throwable)ReflectionUtils.getFieldValue(CustomNettySslTransport.this, "pendingFailure")) != null) {
	                    listener.onTransportError(((Throwable)ReflectionUtils.getFieldValue(CustomNettySslTransport.this, "pendingFailure")));
	                } else {
	                    listener.onTransportError(cause);
	                }
	            } else {
	                // Hold the first failure for later dispatch if connect succeeds.
	                // This will then trigger disconnect using the first error reported.
	                if (((Throwable)ReflectionUtils.getFieldValue(CustomNettySslTransport.this, "pendingFailure")) != null) {
	                    LOG.trace("Holding error until connect succeeds: {}", cause.getMessage());
	                    ReflectionUtils.setFieldValue(CustomNettySslTransport.this, "pendingFailure",cause);
	                }
	            }
	        }

	        @Override
	        protected void channelRead0(ChannelHandlerContext ctx, ByteBuf buffer) throws Exception {
	            LOG.trace("New data read: {} bytes incoming: {}", buffer.readableBytes(), buffer);
	            listener.onData(buffer);
	        }
	    }
}

# 9 通过CustomNettySslTransport的调用自定义的CustomTransportSupport

package org.apache.qpid.jms.transports;

import java.net.URI;
import java.util.HashMap;
import java.util.Map;
import javax.net.ssl.SSLContext;
import io.netty.handler.ssl.SslHandler;

public class CustomTransportSupport extends TransportSupport{
	@SuppressWarnings("rawtypes")
	public static Map mapSSLContext=new HashMap();
	   public static SslHandler createSslHandler(URI remote, TransportSslOptions options) throws Exception {
		   System.out.println("********************************The remote CustomTransportSupport URI:"+remote);
		   SSLContext sslContext=null;
		   if(remote!=null&&mapSSLContext.containsKey(remote.toString())){
			   sslContext=(SSLContext) mapSSLContext.get(remote.toString());
			   return new SslHandler(createSslEngine(remote, sslContext, options));
		   }else{
			   return new SslHandler(createSslEngine(remote, createSslContext(options), options)); 
		   }
	     
	    }
}

终于在可以上面的参数中传入自己心爱的SSLContext的对象了。

恭喜你,终于成功了!!!!


0
0

查看评论
* 以上用户言论只代表其个人观点,不代表CSDN网站的观点或立场
    个人资料
    • 访问:386250次
    • 积分:6352
    • 等级:
    • 排名:第4108名
    • 原创:228篇
    • 转载:0篇
    • 译文:5篇
    • 评论:157条
    博客专栏