com.zextras.modules.chat.server.xmpp.netty.ChatXmppService.java Source code

Java tutorial

Introduction

Here is the source code for com.zextras.modules.chat.server.xmpp.netty.ChatXmppService.java

Source

/*
 * ZAL - The abstraction layer for Zimbra.
 * Copyright (C) 2017 ZeXtras S.r.l.
 *
 * This file is part of ZAL.
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation, version 2 of
 * the License.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with ZAL. If not, see <http://www.gnu.org/licenses/>.
 */

package com.zextras.modules.chat.server.xmpp.netty;

import com.google.inject.Inject;
import com.google.inject.Singleton;
import com.zextras.lib.log.ChatLog;
import com.zextras.modules.chat.properties.ChatProperties;
import com.zextras.modules.chat.server.xmpp.XmppEventFilter;
import com.zextras.modules.chat.server.xmpp.XmppFilterOut;
import com.zextras.modules.core.services.NettyService;
import com.zextras.lib.ZimbraSSLContextProvider;
import com.zextras.lib.switches.Service;
import com.zextras.lib.switches.ServiceName;
import com.zextras.modules.chat.server.events.EventManager;
import com.zextras.modules.chat.server.xmpp.XmppHandlerFactory;
import com.zextras.modules.chat.server.xmpp.encoders.ProxyAuthRequestEncoder;
import com.zextras.modules.chat.server.xmpp.xml.SchemaProvider;
import io.netty.util.concurrent.DefaultProgressivePromise;
import io.netty.util.concurrent.ImmediateEventExecutor;
import io.netty.util.concurrent.Promise;
import org.openzal.zal.Provisioning;
import org.openzal.zal.Utils;
import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelHandler;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelOption;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import io.netty.handler.ssl.SslHandler;

import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLEngine;
import java.net.BindException;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.ReentrantLock;

@Singleton
public class ChatXmppService implements Runnable, Service {
    public static final ServiceName XMPP_SERVICE_NAME = new ServiceName("xmpp");

    private final EventManager mEventManager;
    private final XmppHandlerFactory mXmppHandlerFactory;
    private final SchemaProvider mSchemaProvider;
    private final ZimbraSSLContextProvider mZimbraSSLContextProvider;
    private final ChatProperties mChatProperties;
    private final NettyService mNettyService;
    private final ProxyAuthRequestEncoder mProxyAuthRequestEncoder;
    private final XmppFilterOut mXmppFilterOut;
    private final XmppEventFilter mXmppEventFilter;
    private final Provisioning mProvisioning;
    private boolean mStopped;
    private Promise<Boolean> mInitializationPromise;
    private final ReentrantLock mLock = new ReentrantLock();
    private final Condition mCondition = mLock.newCondition();
    private Thread mThread = null;

    @Inject
    public ChatXmppService(EventManager eventManager, XmppHandlerFactory xmppHandlerFactory,
            SchemaProvider schemaProvider, ZimbraSSLContextProvider zimbraSSLContextProvider,
            ChatProperties chatProperties, NettyService nettyService,
            ProxyAuthRequestEncoder proxyAuthRequestEncoder, XmppFilterOut xmppFilterOut,
            XmppEventFilter xmppEventFilter, Provisioning provisioning) {
        mEventManager = eventManager;
        mXmppHandlerFactory = xmppHandlerFactory;
        mSchemaProvider = schemaProvider;
        mZimbraSSLContextProvider = zimbraSSLContextProvider;
        mChatProperties = chatProperties;
        mNettyService = nettyService;
        mProxyAuthRequestEncoder = proxyAuthRequestEncoder;
        mXmppFilterOut = xmppFilterOut;
        mXmppEventFilter = xmppEventFilter;
        mProvisioning = provisioning;
        mStopped = false;
    }

    @SuppressWarnings("ConstantConditions")
    @Override
    public void start() throws ServiceStartException {
        mInitializationPromise = new DefaultProgressivePromise<Boolean>(ImmediateEventExecutor.INSTANCE);
        mThread = new Thread(this);
        mThread.start();

        try {
            while (true) {
                if (mInitializationPromise.isDone()) {
                    if (!mInitializationPromise.isSuccess()) {
                        throw mInitializationPromise.cause();
                    }
                    return;
                }
                Thread.yield();
            }
        } catch (BindException ex) {
            throw new Service.ServiceStartException(
                    "Cannot bind to ports 5222 and 5223: please make sure that no other process is bound to such ports in order to use any ZeXtras Chat XMPP feature.",
                    ex);
        } catch (Throwable ex) {
            throw new Service.ServiceStartException(ex.getMessage(), ex);
        }
    }

    @Override
    public void stop() {
        shutdown();
    }

    public void run() {
        EventLoopGroup acceptorGroup = new NioEventLoopGroup(4);
        EventLoopGroup channelWorkerGroup = new NioEventLoopGroup(8);

        final boolean useLegacySSLPort = mChatProperties.isChatXmppSslPortEnabled();

        String logListening = "XMPP listening on ports 5222";
        if (useLegacySSLPort) {
            logListening += ", 5223";
        }
        ChatLog.log.info(logListening);

        try {
            SSLContext zimbraSSLContext = mZimbraSSLContextProvider.get();
            ServerBootstrap bootstrapTLS = buildBoostrap(acceptorGroup, channelWorkerGroup, zimbraSSLContext,
                    false);
            ServerBootstrap bootstrapOldSSL = buildBoostrap(acceptorGroup, channelWorkerGroup, zimbraSSLContext,
                    true);

            ChannelFuture futureTLS = bootstrapTLS
                    .bind(mChatProperties.getChatXmppPort(mProvisioning.getLocalServer().getName())).sync(); //addListener
            ChannelFuture futureOldSSL = null;
            if (useLegacySSLPort) {
                futureOldSSL = bootstrapOldSSL
                        .bind(mChatProperties.getChatXmppSslPort(mProvisioning.getLocalServer().getName())).sync();
            }

            ChatLog.log.info("XMPP started");

            mInitializationPromise.setSuccess(true);

            mLock.lock();
            try {
                while (!mStopped) {
                    try {
                        mCondition.await();
                    } catch (InterruptedException ex) {
                    }
                }

                mStopped = false;
            } finally {
                mLock.unlock();
            }

            ChatLog.log.info("XMPP shutting down");

            if (useLegacySSLPort) {
                futureOldSSL.channel().close().sync();
            }
            futureTLS.channel().close().sync();

        } catch (Throwable ex) {
            ChatLog.log.warn(Utils.exceptionToString(ex));
            mInitializationPromise.setFailure(ex);
        } finally {
            acceptorGroup.shutdownGracefully();
            channelWorkerGroup.shutdownGracefully();
        }
    }

    public void shutdown() {
        Thread thread;
        mLock.lock();
        try {
            mStopped = true;
            mCondition.signalAll();
            thread = mThread;
            mThread = null;
        } finally {
            mLock.unlock();
        }

        try {
            if (thread != null) {
                thread.join();
            }
        } catch (InterruptedException e) {
        }
    }

    private ServerBootstrap buildBoostrap(EventLoopGroup acceptorGroup, EventLoopGroup workerGroup,
            final SSLContext zimbraSSLContext, final boolean oldSSL) {

        ServerBootstrap serverBootstrap = new ServerBootstrap();
        serverBootstrap.group(acceptorGroup, workerGroup);

        serverBootstrap.channel(NioServerSocketChannel.class);

        ChannelHandler handler = new ChannelInitializer<SocketChannel>() {
            @Override
            public void initChannel(SocketChannel ch) throws Exception {
                try {
                    if (oldSSL) {
                        final SSLEngine engine = zimbraSSLContext.createSSLEngine();
                        engine.setUseClientMode(false);
                        ch.pipeline().addFirst(null, "SSL", new SslHandler(engine, false));
                    }

                    ch.pipeline().addLast(null, "SubTagTokenizer", new XmlSubTagTokenizer());
                    FirstTags firstTagsHandler = new FirstTags(mXmppHandlerFactory, mEventManager, ch,
                            mSchemaProvider, zimbraSSLContext, oldSSL, mChatProperties, mNettyService,
                            mProxyAuthRequestEncoder, mXmppEventFilter, mXmppFilterOut);
                    ch.pipeline().addAfter("SubTagTokenizer", "FirstTags", firstTagsHandler);
                } catch (Throwable ex) {
                    ChatLog.log.warn("Unable to initialize XMPP connection: " + Utils.exceptionToString(ex));
                    ch.close();
                }
            }
        };

        serverBootstrap.childHandler(handler).option(ChannelOption.SO_BACKLOG, 128)
                .childOption(ChannelOption.SO_KEEPALIVE, true).childOption(ChannelOption.CONNECT_TIMEOUT_MILLIS, 0);

        return serverBootstrap;
    }
}