com.liferay.portal.search.elasticsearch.internal.connection.EmbeddedElasticsearchConnection.java Source code

Java tutorial

Introduction

Here is the source code for com.liferay.portal.search.elasticsearch.internal.connection.EmbeddedElasticsearchConnection.java

Source

/**
 * Copyright (c) 2000-present Liferay, Inc. All rights reserved.
 *
 * This library is free software; you can redistribute it and/or modify it under
 * the terms of the GNU Lesser General Public License as published by the Free
 * Software Foundation; either version 2.1 of the License, or (at your option)
 * any later version.
 *
 * This library 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 Lesser General Public License for more
 * details.
 */

package com.liferay.portal.search.elasticsearch.internal.connection;

import com.liferay.portal.configuration.metatype.bnd.util.ConfigurableUtil;
import com.liferay.portal.kernel.log.Log;
import com.liferay.portal.kernel.log.LogFactoryUtil;
import com.liferay.portal.kernel.security.SecureRandomUtil;
import com.liferay.portal.kernel.util.File;
import com.liferay.portal.kernel.util.PortalRunMode;
import com.liferay.portal.kernel.util.Props;
import com.liferay.portal.kernel.util.PropsKeys;
import com.liferay.portal.kernel.util.StringBundler;
import com.liferay.portal.kernel.util.SystemProperties;
import com.liferay.portal.kernel.util.Validator;
import com.liferay.portal.search.elasticsearch.configuration.ElasticsearchConfiguration;
import com.liferay.portal.search.elasticsearch.connection.ElasticsearchConnection;
import com.liferay.portal.search.elasticsearch.connection.OperationMode;
import com.liferay.portal.search.elasticsearch.index.IndexFactory;
import com.liferay.portal.search.elasticsearch.internal.cluster.ClusterSettingsContext;
import com.liferay.portal.search.elasticsearch.settings.SettingsContributor;

import java.io.IOException;

import java.net.InetAddress;

import java.util.Iterator;
import java.util.Map;
import java.util.concurrent.RejectedExecutionHandler;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

import org.apache.commons.lang.time.StopWatch;

import org.elasticsearch.client.Client;
import org.elasticsearch.common.inject.Injector;
import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.discovery.DiscoveryService;
import org.elasticsearch.index.IndexService;
import org.elasticsearch.index.settings.IndexSettingsService;
import org.elasticsearch.indices.IndicesService;
import org.elasticsearch.node.Node;
import org.elasticsearch.node.NodeBuilder;
import org.elasticsearch.search.SearchService;
import org.elasticsearch.search.action.SearchServiceTransportAction;
import org.elasticsearch.search.internal.ShardSearchTransportRequest;
import org.elasticsearch.threadpool.ThreadPool;
import org.elasticsearch.transport.TransportChannel;
import org.elasticsearch.transport.TransportRequestHandler;
import org.elasticsearch.transport.TransportService;

import org.jboss.netty.util.internal.ByteBufferUtil;

import org.osgi.framework.BundleContext;
import org.osgi.service.component.annotations.Activate;
import org.osgi.service.component.annotations.Component;
import org.osgi.service.component.annotations.Deactivate;
import org.osgi.service.component.annotations.Modified;
import org.osgi.service.component.annotations.Reference;
import org.osgi.service.component.annotations.ReferenceCardinality;
import org.osgi.service.component.annotations.ReferencePolicy;
import org.osgi.service.component.annotations.ReferencePolicyOption;

/**
 * @author Michael C. Han
 */
@Component(configurationPid = "com.liferay.portal.search.elasticsearch.configuration.ElasticsearchConfiguration", immediate = true, property = {
        "operation.mode=EMBEDDED" }, service = ElasticsearchConnection.class)
public class EmbeddedElasticsearchConnection extends BaseElasticsearchConnection {

    @Override
    public void close() {
        super.close();

        if (_node == null) {
            return;
        }

        try {
            Class.forName(ByteBufferUtil.class.getName());
        } catch (ClassNotFoundException cnfe) {
            if (_log.isWarnEnabled()) {
                _log.warn(
                        StringBundler.concat("Unable to preload ", String.valueOf(ByteBufferUtil.class),
                                " to prevent Netty shutdown concurrent class loading ", "interruption issue"),
                        cnfe);
            }
        }

        if (PortalRunMode.isTestMode()) {
            settingsBuilder.put("index.refresh_interval", "-1");
            settingsBuilder.put("index.translog.flush_threshold_ops", Integer.MAX_VALUE);
            settingsBuilder.put("index.translog.interval", "1d");

            Settings settings = settingsBuilder.build();

            Injector injector = _node.injector();

            IndicesService indicesService = injector.getInstance(IndicesService.class);

            Iterator<IndexService> iterator = indicesService.iterator();

            while (iterator.hasNext()) {
                IndexService indexService = iterator.next();

                injector = indexService.injector();

                IndexSettingsService indexSettingsService = injector.getInstance(IndexSettingsService.class);

                indexSettingsService.refreshSettings(settings);
            }

            ThreadPool threadPool = injector.getInstance(ThreadPool.class);

            ScheduledExecutorService scheduledExecutorService = threadPool.scheduler();

            if (scheduledExecutorService instanceof ThreadPoolExecutor) {
                ThreadPoolExecutor threadPoolExecutor = (ThreadPoolExecutor) scheduledExecutorService;

                threadPoolExecutor.setRejectedExecutionHandler(_REJECTED_EXECUTION_HANDLER);
            }

            scheduledExecutorService.shutdown();

            try {
                scheduledExecutorService.awaitTermination(1, TimeUnit.HOURS);
            } catch (InterruptedException ie) {
                if (_log.isWarnEnabled()) {
                    _log.warn("Thread pool shutdown wait was interrupted", ie);
                }
            }
        }

        _node.close();

        _node = null;

        _file.deltree(_jnaTmpDirName);
    }

    public Node getNode() {
        return _node;
    }

    @Override
    public OperationMode getOperationMode() {
        return OperationMode.EMBEDDED;
    }

    @Override
    @Reference(unbind = "-")
    public void setIndexFactory(IndexFactory indexFactory) {
        super.setIndexFactory(indexFactory);
    }

    @Activate
    @Modified
    protected void activate(BundleContext bundleContext, Map<String, Object> properties) {

        elasticsearchConfiguration = ConfigurableUtil.createConfigurable(ElasticsearchConfiguration.class,
                properties);

        java.io.File tempDir = bundleContext.getDataFile(JNA_TMP_DIR);

        _jnaTmpDirName = tempDir.getAbsolutePath();
    }

    @Override
    @Reference(cardinality = ReferenceCardinality.MULTIPLE, policy = ReferencePolicy.DYNAMIC, policyOption = ReferencePolicyOption.GREEDY, target = "(operation.mode=EMBEDDED)")
    protected void addSettingsContributor(SettingsContributor settingsContributor) {

        super.addSettingsContributor(settingsContributor);
    }

    protected void configureClustering() {
        settingsBuilder.put("cluster.name", elasticsearchConfiguration.clusterName());
        settingsBuilder.put("cluster.routing.allocation.disk.threshold_enabled", false);
        settingsBuilder.put("discovery.zen.ping.multicast.enabled", false);
    }

    protected void configureHttp() {
        settingsBuilder.put("http.enabled", elasticsearchConfiguration.httpEnabled());

        if (!elasticsearchConfiguration.httpEnabled()) {
            return;
        }

        settingsBuilder.put("http.cors.enabled", elasticsearchConfiguration.httpCORSEnabled());

        if (!elasticsearchConfiguration.httpCORSEnabled()) {
            return;
        }

        settingsBuilder.put("http.cors.allow-origin", elasticsearchConfiguration.httpCORSAllowOrigin());

        String httpCORSConfigurations = elasticsearchConfiguration.httpCORSConfigurations();

        if (Validator.isNotNull(httpCORSConfigurations)) {
            settingsBuilder.loadFromSource(httpCORSConfigurations);
        }
    }

    protected void configureNetworking() {
        String networkBindHost = elasticsearchConfiguration.networkBindHost();

        if (Validator.isNotNull(networkBindHost)) {
            settingsBuilder.put("network.bind.host", networkBindHost);
        }

        String networkHost = elasticsearchConfiguration.networkHost();

        if (Validator.isNull(networkBindHost) && Validator.isNull(networkHost)
                && Validator.isNull(elasticsearchConfiguration.networkPublishHost())) {

            InetAddress localBindInetAddress = clusterSettingsContext.getLocalBindInetAddress();

            if (localBindInetAddress != null) {
                networkHost = localBindInetAddress.getHostAddress();
            }
        }

        if (Validator.isNotNull(networkHost)) {
            settingsBuilder.put("network.host", networkHost);
        }

        String networkPublishHost = elasticsearchConfiguration.networkPublishHost();

        if (Validator.isNotNull(networkPublishHost)) {
            settingsBuilder.put("network.publish.host", networkPublishHost);
        }

        String transportTcpPort = elasticsearchConfiguration.transportTcpPort();

        if (Validator.isNotNull(transportTcpPort)) {
            settingsBuilder.put("transport.tcp.port", transportTcpPort);
        }
    }

    protected void configurePaths() {
        settingsBuilder.put("path.data", props.get(PropsKeys.LIFERAY_HOME) + "/data/elasticsearch/indices");
        settingsBuilder.put("path.home", props.get(PropsKeys.LIFERAY_HOME) + "/data/elasticsearch");
        settingsBuilder.put("path.logs", props.get(PropsKeys.LIFERAY_HOME) + "/logs");
        settingsBuilder.put("path.plugins", props.get(PropsKeys.LIFERAY_HOME) + "/data/elasticsearch/plugins");
        settingsBuilder.put("path.repo", props.get(PropsKeys.LIFERAY_HOME) + "/data/elasticsearch/repo");
        settingsBuilder.put("path.work", SystemProperties.get(SystemProperties.TMP_DIR));
    }

    protected void configurePlugin(String name, Settings settings) {
        EmbeddedElasticsearchPluginManager embeddedElasticsearchPluginManager = createEmbeddedElasticsearchPluginManager(
                name, settings);

        try {
            embeddedElasticsearchPluginManager.install();
        } catch (IOException ioe) {
            throw new RuntimeException("Unable to install " + name + " plugin", ioe);
        }
    }

    protected void configurePlugins() {
        Settings settings = settingsBuilder.build();

        String[] plugins = { "analysis-icu", "analysis-kuromoji", "analysis-smartcn", "analysis-stempel" };

        for (String plugin : plugins) {
            removeObsoletePlugin(plugin, settings);
        }

        for (String plugin : plugins) {
            configurePlugin(plugin, settings);
        }
    }

    @Override
    protected Client createClient() {
        StopWatch stopWatch = new StopWatch();

        stopWatch.start();

        if (_log.isWarnEnabled()) {
            StringBundler sb = new StringBundler(6);

            sb.append("Liferay is configured to use embedded Elasticsearch ");
            sb.append("as its search engine. Do NOT use embedded ");
            sb.append("Elasticsearch in production. Embedded Elasticsearch ");
            sb.append("is useful for development and demonstration purposes. ");
            sb.append("Remote Elasticsearch connections can be configured in ");
            sb.append("the Control Panel.");

            _log.warn(sb.toString());
        }

        if (_log.isDebugEnabled()) {
            _log.debug("Starting embedded Elasticsearch cluster " + elasticsearchConfiguration.clusterName());
        }

        _node = createNode(settingsBuilder.build());

        _node.start();

        Client client = _node.client();

        if (_log.isDebugEnabled()) {
            stopWatch.stop();

            _log.debug(StringBundler.concat("Finished starting ", elasticsearchConfiguration.clusterName(), " in ",
                    String.valueOf(stopWatch.getTime()), " ms"));
        }

        return client;
    }

    protected EmbeddedElasticsearchPluginManager createEmbeddedElasticsearchPluginManager(String name,
            Settings settings) {

        return new EmbeddedElasticsearchPluginManager(name, settings.get("path.plugins"),
                new PluginManagerFactoryImpl(settings), new PluginZipFactoryImpl());
    }

    protected Node createNode(Settings settings) {
        Thread thread = Thread.currentThread();

        ClassLoader contextClassLoader = thread.getContextClassLoader();

        Class<?> clazz = getClass();

        thread.setContextClassLoader(clazz.getClassLoader());

        String jnaTmpDir = System.getProperty("jna.tmpdir");

        System.setProperty("jna.tmpdir", _jnaTmpDirName);

        try {
            NodeBuilder nodeBuilder = new NodeBuilder();

            nodeBuilder.settings(settings);

            nodeBuilder.local(true);

            Node node = nodeBuilder.build();

            if (elasticsearchConfiguration.syncSearch()) {
                Injector injector = node.injector();

                _replaceTransportRequestHandler(injector.getInstance(TransportService.class),
                        injector.getInstance(SearchService.class));
            }

            return node;
        } finally {
            thread.setContextClassLoader(contextClassLoader);

            if (jnaTmpDir == null) {
                System.clearProperty("jna.tmpdir");
            } else {
                System.setProperty("jna.tmpdir", jnaTmpDir);
            }
        }
    }

    @Deactivate
    protected void deactivate(Map<String, Object> properties) {
        close();
    }

    @Override
    protected void loadRequiredDefaultConfigurations() {
        settingsBuilder.put("action.auto_create_index", false);
        settingsBuilder.put("bootstrap.mlockall", elasticsearchConfiguration.bootstrapMlockAll());

        configureClustering();

        configureHttp();

        settingsBuilder.put("index.number_of_replicas", 0);
        settingsBuilder.put("index.number_of_shards", 1);

        configureNetworking();

        settingsBuilder.put("node.client", false);
        settingsBuilder.put("node.data", true);
        settingsBuilder.put(DiscoveryService.SETTING_DISCOVERY_SEED, SecureRandomUtil.nextLong());

        configurePaths();

        configurePlugins();

        if (PortalRunMode.isTestMode()) {
            settingsBuilder.put("index.refresh_interval", "1ms");
            settingsBuilder.put("index.search.slowlog.threshold.fetch.warn", "-1");
            settingsBuilder.put("index.search.slowlog.threshold.query.warn", "-1");
            settingsBuilder.put("index.translog.flush_threshold_ops", "1");
            settingsBuilder.put("index.translog.interval", "1ms");
            settingsBuilder.put("monitor.jvm.enabled", Boolean.FALSE.toString());
        }
    }

    protected void removeObsoletePlugin(String name, Settings settings) {
        EmbeddedElasticsearchPluginManager embeddedElasticsearchPluginManager = createEmbeddedElasticsearchPluginManager(
                name, settings);

        try {
            embeddedElasticsearchPluginManager.removeObsoletePlugin();
        } catch (IOException ioe) {
            throw new RuntimeException("Unable to remove " + name + " plugin", ioe);
        }
    }

    @Override
    protected void removeSettingsContributor(SettingsContributor settingsContributor) {

        super.removeSettingsContributor(settingsContributor);
    }

    protected static final String JNA_TMP_DIR = "elasticSearch-tmpDir";

    @Reference
    protected ClusterSettingsContext clusterSettingsContext;

    @Reference
    protected Props props;

    private void _replaceTransportRequestHandler(TransportService transportService, SearchService searchService) {

        String action = SearchServiceTransportAction.QUERY_FETCH_ACTION_NAME;

        transportService.removeHandler(action);

        transportService.registerRequestHandler(action, ShardSearchTransportRequest.class, ThreadPool.Names.SAME,
                new TransportRequestHandler<ShardSearchTransportRequest>() {

                    @Override
                    public void messageReceived(ShardSearchTransportRequest shardSearchTransportRequest,
                            TransportChannel transportChannel) throws Exception {

                        transportChannel.sendResponse(searchService.executeFetchPhase(shardSearchTransportRequest));
                    }

                });
    }

    /**
     * Keep this as a static field to avoid the class loading failure during
     * Tomcat shutdown.
     */
    private static final RejectedExecutionHandler _REJECTED_EXECUTION_HANDLER = new RejectedExecutionHandler() {

        @Override
        public void rejectedExecution(Runnable runnable, ThreadPoolExecutor threadPoolExecutor) {

            if (_log.isInfoEnabled()) {
                _log.info(StringBundler.concat("Discarded ", String.valueOf(runnable), " on ",
                        String.valueOf(threadPoolExecutor)));
            }
        }

    };

    private static final Log _log = LogFactoryUtil.getLog(EmbeddedElasticsearchConnection.class);

    private static String _jnaTmpDirName;

    @Reference
    private File _file;

    private Node _node;

}